web-dev-qa-db-ja.com

匿名型の非読み取り専用の代替

C#では、匿名型は次のようになります。

method doStuff(){
     var myVar = new {
         a = false, 
         b = true
     }

     if (myVar.a) 
     {
         // Do stuff             
     }
}

ただし、次のものはコンパイルされません。

method doStuff(){
     var myVar = new {
         a = false, 
         b = true
     }

     if (myVar.a) 
     {
         myVar.b = true;
     }
}

これは、myVarのフィールドが読み取り専用であり、割り当てられないためです。後者のようなことをしたいことはかなり一般的です。おそらく、私が見た最善の解決策は、メソッドの外で構造体を定義することです。

しかし、上記のブロックを機能させる他の方法は本当にありませんか?気になるのは、myVarがこのフィールドのローカル変数であるため、それを使用するメソッド内でのみ参照する必要があるようです。さらに、構造体をメソッドの外側に配置する必要があると、特に長いメソッドでは、オブジェクトの宣言がその使用からかなり遠ざかる可能性があります。

別の言い方をすると、このような「構造体」を定義できる匿名型の代替はありますか(構造体はC#に存在し、メソッドの外部で定義する必要があることを理解しています)、それを読み取り専用にすることはありませんか?いいえの場合、これを実行したいという根本的な問題はありますか。別の方法を使用する必要がありますか?

34
Superbest

いいえ、これを行うには、独自のクラスまたは構造体を作成する必要があります(クラスを変更可能にする場合は、できればクラスを使用してください。変更可能な構造体は恐ろしいものです)。

Equals/ToString/GetHashCodeの実装を気にしない場合は、非常に簡単です。

public class MyClass {
    public bool Foo { get; set; }
    public bool Bar { get; set; }
}

(まだフィールドではなくプロパティを使用します さまざまな理由から 。)

個人的には通常、メソッド間で渡すことができるimmutable型が必要なことに気づきます-既存の匿名型機能の名前付きバージョンが必要です...

34
Jon Skeet

読み取り専用にせずに、このような単純な「レコード」タイプを簡潔に定義できる匿名タイプの代替はありますか?

いいえ。名義型を作成する必要があります。

いいえの場合、これを実行したいという根本的な問題はありますか?

いいえ、これは以前に検討した合理的な機能です。

Visual Basicでは、匿名型areは、必要に応じて変更可能です。

変更可能な匿名型について本当に「根本的に間違っている」唯一のことは、ハッシュ型として使用することが危険であることです。匿名型は、(1)LINQクエリ内包の等価結合でキーとして使用すること、および(2)LINQ-to-Objectsおよびその他の実装では、ハッシュテーブルを使用して結合が実装されることを前提に設計されています。したがって、匿名型はハッシュキーとして役立つはずであり、変更可能なハッシュキーは危険です。

Visual Basicでは、GetHashCode実装は匿名型の可変フィールドからの情報を消費しません。これは妥当な妥協策ですが、C#では、余分な複雑さは努力に値しないと判断しました。

26
Eric Lippert

C#7では 名前付きタプル を活用してトリックを実行できます:

(bool a, bool b) myVar = (false, true);

if (myVar.a)
{
    myVar.b = true;
}
6
Kees C. Bakker

Nice初期化構文を取得することはできませんが、.NET 4で導入されたExpandoObjectクラスは実行可能なソリューションとして機能します。

dynamic eo = new ExpandoObject();

eo.SomeIntValue = 5;
eo.SomeIntValue = 10; // works fine
3
AnthonyOSX

上記のタイプの操作では、独自のミュータブルを定義する必要があります 構造。ミュータブルな構造体は、Eric Lippertのようなコンパイラー作成者にとって頭痛の種となる可能性があり、.netがそれらを処理する方法にはいくつかの残念な制限がありますが、それでもミュータブルな "Plain Old Data"構造体(すべてのフィールドがパブリックで唯一の構造体)のセマンティクスthisを書き込むパブリック関数はコンストラクターであるか、コンストラクターから排他的に呼び出されます)クラスを介して実現できるよりもはるかに明確なセマンティクスを提供します。

たとえば、次のことを考慮してください。

 struct Foo {
 public int bar; 
 ...その他のもの; 
} 
 int test(Action <Foo []> proc1、Action <Foo> proc2)
 {
 foo myFoos [] = new Foo [100]; 
 proc1(myFoos); 
 myFoos [4] .bar = 9; 
 proc2(myFoos [4]); //値渡し
 return myFoos [4] .bar; 
} 

安全でないコードがなく、渡されたデリゲートを呼び出すことができ、有限の時間で戻ると仮定すると、test()は何を返しますか? Fooがパブリックフィールドbarを持つ構造体であるという事実は、質問に答えるのに十分です:Fooの宣言に他に何があるかに関係なく、9を返します。 _proc1_および_proc2_で渡される関数に関係なく。 Fooがクラスである場合、test()が何を返すかを知るために、存在する、またはこれから存在する_Action<Foo[]>_および_Action<Foo>_をすべて調べる必要があります。 Fooがパブリックフィールドbarを持つ構造体であると判断することは、渡される可能性のある過去および将来のすべての関数を調べるよりもはるかに簡単に思えます。

thisを変更する構造体メソッドは.netでは特に扱いが悪いため、メソッドを使用して構造体を変更する必要がある場合は、ほぼ確実に次のパターンのいずれかを使用することをお勧めします。

 myStruct = myStruct.ModifiedInSomeFashion(...); //アプローチ#1 
 myStructType.ModifyInSomeFashion(ref myStruct、...); //アプローチ#2 

パターンより:

 myStruct.ModifyInSomeFashion(...); 

上記のアプローチを使用して構造体を変更するパターンを提供する場合、ただし、可変構造体には、不変構造体または不変クラスよりも効率的で読みやすいコードを使用できるという利点があり、可変クラスよりも問題が発生しにくくなります。値の集約を表すもので、それらに含まれる値の外にIDがない場合、可変クラス型は、多くの場合、可能な限り最悪の表現です。

2
supercat

VB-多くの場合、EF/LINQプロジェクションを使用してデータベースからデータを返したいので、匿名のプロパティを読み取り/書き込みとして設定できないことは本当に不愉快です何らかの理由でデータベースで実行できないc#のデータの一部のマッサージ。これを行う最も簡単な方法は、既存の匿名インスタンスを反復処理してプロパティを更新することです。これは、現在EFではそれほど問題ではありません。コア、最終的に1つのクエリでdb関数と.net関数を混在させることができます。

私の主な回避策は、リフレクションを使用することです。基礎となる実装が変更され、すべてのコードが壊れた場合、購入者は注意してください。

public static class AnonClassHelper {

    public static void SetField<T>(object anonClass, string fieldName, T value) {
        var field = anonClass.GetType().GetField($"<{fieldName}>i__Field", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        field.SetValue(anonClass, value);
    }

}
// usage
AnonClassHelper.SetField(inst, nameof(inst.SomeField), newVal);

文字列を処理するときに使用した代替方法は、StringBuilder型のプロパティを作成することです。これらの個別のプロパティは、匿名型のインスタンスを作成した後、StringBuilderメソッドを介して設定できます。

0
LMK