web-dev-qa-db-ja.com

コンストラクターから呼び出される初期化メソッドに読み取り専用フィールドを設定するにはどうすればよいですか?

Init()メソッドの上にある属性を使用して、次のことができることをどこかで見たことがあると確信しています。これにより、コンパイラーに、Init()メソッドはコンストラクターからのみ呼び出す必要があるため、読み取り専用フィールドで次のことが可能になります。悩ませる。属性の名前を忘れてしまいましたが、グーグルで見つけられないようです。

public class Class
{
    private readonly int readonlyField;

    public Class()
    {
        Init();
    }

    // Attribute here that tells the compiler that this method must be called only from a constructor
    private void Init()
    {
        readonlyField = 1;
    }
}
36
Ben Anderson

ロブの答え 私の本ではそれを行う方法です。複数のフィールドを初期化する必要がある場合は、outパラメーターを使用して初期化できます。

public class Class
{
    private readonly int readonlyField1;
    private readonly int readonlyField2;

    public Class()
    {
        Init(out readonlyField1, out readonlyField2);
    }

    protected virtual void Init(out int field1, out int field2)
    {
        field1 = 1;
        field2 = 2;
    }
}

個人的には、これは特定のシナリオで意味があると思います。たとえば、フィールドをreadonlyにしたいが、できるようにしたい場合などです。派生クラスでそれらを異なる方法で設定します(protectedコンストラクターを介して大量のパラメーターをチェーンする必要はありません)。しかし、多分それは私だけです。

42
Dan Tao

Initializeメソッドを使用する代わりに、他のすべてのコンストラクターを介して基本的なコンストラクターを継承するのはどうでしょうか。つまり.

public class MyClass
{
    readonly int field1;
    readonly double field2;
    public MyClass(int field1, double field2)
    {
        //put whatever initialization logic you need here...
        field1 = 10;
        field2 = 30.2;
    }
    public MyClass(int field1, double field2p1, double field2p2)
        : this(field1, (field2p1 + field2p2))
    {
        //put anything extra in here
    }
}

これは、困っている元の人に到達するのに少し遅れるかもしれませんが、これは問題をきれいに解決するようです...どんな種類の厄介な反射や出力パラメータを使用する必要もありません。

13
tvt173

私が考えることができる唯一の解決策は、readonlyフィールドに割り当てる必要があるInit()メソッドから値を返すことです。

public class Class
{
    private readonly int readonlyField;

    public Class()
    {
        readonlyField = Init();
    }

    private int Init()
    {
        return 1;
    }
}
12
Rob

これはできません。 readonlyでタグ付けされたフィールドは、コンストラクターからのみ設定できます

6
JaredPar

Jaredは正しいです。これは不可能です。私が考えることができる回避策は次のとおりです。

  1. 宣言のフィールドを初期化します。
  2. コンストラクターでフィールドを初期化します(Initメソッドを手動でインライン化します)。
  3. メソッドによって返される値にフィールドを割り当てます。例:_myField = GetInitialMyFieldValue();
  4. Init修飾子を使用して、フィールドをoutメソッドに渡します。これは、コンストラクターのパラメーターに依存する、初期化するフィールドが多数ある場合に役立つことがあります。例えば。

 private readonly int _x;
 private readonly string _y;

 private void Init(int someConstructorParam, out int x, out string y){ .. }

 public Class(int someConstructorParam)
 {
     Init(someConstructorParam, out _x, out _y);
 } 
5
Ani

私が現在の技術(C#7.x)でやったことは、値タプルシステムを使用することです。

public class MyClass
{
    private readonly int x;
    private readonly int y;
    private readonly int z;

    public MyClass(int x)
    {
        this.x = x;
        (y, z) = InitYandZ();
    }

    private (int, int) InitYandZ()
    {
        return (5, 10);
    }
}

どちらもきれいではありませんが、私にはきれいに見えます。

2
Ran Sagy

C#コンパイラでは、読み取り専用フィールドをインラインで初期化する場合にのみ、それらを設定できます。

private readonly int readonlyField = 1;

またはコンストラクターから:

public Class()
{
    readonlyField = 1;
}
1

ゲッターのみで初期化されたプロパティはどうですか(C#6.0以降)?

    private int MyProperty { get; } = 0;
0
Greg

Reflectionを使えばうまくいくと思います。実際、これは私にとってはうまくいきます:

    public class Class
        {
            private readonly int readonlyField;
        public int MyField()
        {
             return readonlyField;
        }
        public Class()
        {
            readonlyField = 9;
        }
    }

そして

static void Main(string[] args)
        {

            Class classObj = new Class();
            Console.WriteLine(classObj.MyField());//9

            Misc.SetVariableyByName(classObj, "readonlyField", 20);//20
            Console.WriteLine(classObj.MyField());
         }

これはSetVariableByName()です:

public static b

ool SetVariableyByName(object obj, string var_name, object value)
            {
                FieldInfo info = obj.GetType().GetField(var_name, BindingFlags.NonPublic| BindingFlags.Instance);
                if (info == null)
                return false;
            /* ELSE */
            info.SetValue(obj, value);
            return true;          
        }

唯一のことは、readonlyFieldはプライベートではなくパブリックであるということです。プライベートフィールドを編集できることは知っていますが、なぜそれが機能しないのかわかりません!

0
Derar

これが遅いことは知っていますが、複数のフィールドを初期化する必要がある場合、パラメータを使用する代わりにクラスを戻り値として使用するのはどうですか?欠点はありますか?これは、ネストされたコンストラクターを使用するよりも便利で読みやすいです。

public class MyInitClass
{
    public int Field1 { get; set; }
    public int Field2 { get; set; }
}

public class Class
{
    private readonly int readonlyField1;
    private readonly int readonlyField2;

    public Class()
    {
        var init = Init();
        readonlyField1 = init.Field1;
        readonlyField2 = init.Field2;
    }

    private MyInitClass Init()
    {
        return new MyInitClass() { Field1 = 1, Field2 = 2 };
    }
}
0
user1106591