web-dev-qa-db-ja.com

C#で匿名型のプロパティにアクセスするにはどうすればよいですか?

私はこれを持っています:

_List<object> nodes = new List<object>(); 

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});
_

...そして、匿名オブジェクトの「チェック済み」プロパティを取得できるかどうか疑問に思っています。これが可能かどうかはわかりません。これをやってみました:

if (nodes.Any(n => n["Checked"] == false)) ...しかし、機能しません。

ありがとう

105
wgpubs

匿名型の強く型付けされたリストが必要な場合は、リストも匿名型にする必要があります。これを行う最も簡単な方法は、配列などのシーケンスをリストに投影することです。

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

次に、次のようにアクセスできるようになります。

nodes.Any(n => n.Checked);

コンパイラの動作方法により、匿名型は同じ構造を持ち、同じ型であるため、リストを作成すると、次も動作するはずです。ただし、これを検証するためのコンパイラはありません。

nodes.Add(new { Checked = false, /* etc */ });
57
Greg Beech

オブジェクトをobject型として保存している場合、リフレクションを使用する必要があります。これは、匿名またはその他のあらゆるオブジェクトタイプに当てはまります。オブジェクトoで、そのタイプを取得できます。

Type t = o.GetType();

次に、そこからプロパティを検索します。

PropertyInfo p = t.GetProperty("Foo");

次に、そこから値を取得できます。

object v = p.GetValue(o, null);

この答えは、C#4の更新版には長らく待ち望まれていました。

dynamic d = o;
object v = d.Foo;

そして今、C#6の別の代替手段:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

?.を使用すると、3つの異なる状況でvnullになることに注意してください!

  1. onullなので、オブジェクトはまったくありません
  2. onullではないが、プロパティFooを持たない
  3. oにはプロパティFooがありますが、実際の値はたまたまnullです。

したがって、これは前の例と同等ではありませんが、3つのケースすべてを同じように扱いたい場合には意味があります。

240

Reflectionを使用して、匿名型のプロパティを反復処理できます。 「チェック済み」プロパティがあるかどうかを確認し、ある場合はその値を取得します。

このブログ投稿を参照してください: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand .aspx

のようなもの:

foreach(object o in nodes)
{
    Type t = o.GetType();

    PropertyInfo[] pi = t.GetProperties(); 

    foreach (PropertyInfo p in pi)
    {
        if (p.Name=="Checked" && !(bool)p.GetValue(o))
            Console.WriteLine("awesome!");
    }
}
13
glennkentwell

受け入れられた回答は、リストの宣言方法を正しく説明しており、ほとんどのシナリオで強く推奨されています。

しかし、私は別のシナリオに出くわしました。これは、質問もカバーしています。 MVCのViewData["htmlAttributes"]のような既存のオブジェクトリストを使用する必要がある場合はどうなりますか?プロパティにアクセスするにはどうすればよいですか(通常、new { @style="width: 100px", ... }で作成されます)?

このわずかに異なるシナリオについて、私が見つけたものをあなたと共有したいと思います。以下のソリューションでは、nodesの次の宣言を想定しています。

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

1.動的なソリューション

C#4.0以降バージョンでは、動的にキャストして次のように記述することができます。

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found not checked element!");

注:これはlate binding、を使用しています。つまり、オブジェクトにCheckedプロパティがなく、この場合はRuntimeBinderExceptionをスローする場合にのみ実行時に認識されます。存在しないChecked2プロパティを使用しようとすると、次のメッセージ実行時:"'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'"が表示されます。

2.リフレクションを使用したソリューション

リフレクションを使用したソリューションは、古いC#コンパイラと新しいC#コンパイラの両方バージョンで機能します。古いC#バージョンについては、この回答の最後にあるヒントを参考にしてください。

バックグラウンド

出発点として、私は良い答えを見つけました here 。その考えは、リフレクションを使用して匿名データ型を辞書に変換することです。ディクショナリを使用すると、プロパティの名前がキーとして保存されるため、プロパティに簡単にアクセスできます(myDict["myProperty"]のようにアクセスできます)。

上記のリンクのコードに触発されて、拡張メソッドとしてGetPropUnanonymizeProperties、およびUnanonymizeListItemsを提供する拡張クラスを作成しました。これにより、匿名プロパティへのアクセスが簡単になります。このクラスを使用すると、次のようにクエリを実行できます。

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

または、式nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()if条件として使用できます。これは暗黙的にフィルター処理され、返される要素があるかどうかを確認します。

「Checked」プロパティを含む最初のオブジェクトを取得し、そのプロパティ「depth」を返すには、次を使用できます。

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

以下:nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

注:すべてのプロパティを含む必要のないオブジェクトのリストがあり(たとえば、一部のプロパティが「チェック済み」プロパティを含まない)、「チェック済み」値、これを行うことができます:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

これにより、「Checked」プロパティが存在しない場合にKeyNotFoundExceptionが発生することを防ぎます。


以下のクラスには、次の拡張メソッドが含まれています。

  • UnanonymizeProperties:オブジェクトに含まれるプロパティの匿名化解除に使用されます。この方法は反射を使用します。オブジェクトを、プロパティとその値を含む辞書に変換します。
  • UnanonymizeListItems:オブジェクトのリストを、プロパティを含む辞書のリストに変換するために使用されます。オプションで、事前にフィルタリングするラムダ式を含めることができます。
  • GetProp:指定されたプロパティ名に一致する単一の値を返すために使用されます。存在しないプロパティをKeyNotFoundException(false)ではなくnull値(true)として扱うことができます。

上記の例で必要なことは、以下の拡張クラスを追加することだけです。

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object obj)
    {
        Type type = obj?.GetType();
        var properties = type?.GetProperties()
               ?.Select(n => n.Name)
               ?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj, null));
        return properties;
    }

    // converts object list into list of properties that meet the filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<IDictionary>();
        foreach (object obj in objectList)
        {
            var props = obj.UnanonymizeProperties();
            if (filterCriteria == default
               || filterCriteria((IDictionary<string, object>)props) == true)
            { accessibleList.Add(props); }
        }
        return accessibleList;
    }

    // returns specific property, i.e. obj.GetProp(propertyName)
    // requires prior usage of AccessListItems and selection of one element, because
    // object needs to be a IDictionary<string, object>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

ヒント:上記のコードは、C#バージョン6.0以降で使用可能な null-conditional 演算子を使用しています-古いC#コンパイラ(C#3.0など)で作業している場合、は、単に?..に、?[[に置き換えます。たとえば、.

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

notを使用して古いC#コンパイラを使用する場合、null条件を使用するとnullの処理がはるかに簡単になるため、そのままにしてください。

注:動的な他のソリューションと同様に、このソリューションも遅延バインディングを使用していますが、この場合は例外を取得していません-非参照を参照している場合、単に要素を見つけられません null-conditional 演算子を保持する限り、既存のプロパティ。

アプリケーションによっては、ソリューション2の文字列を介してプロパティが参照されるため、パラメーター化できる場合があります。

1
Matt

最近、.NET 3.5で同じ問題が発生しました(使用可能な動的なし)。ここに私が解決した方法があります:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Stackoverflowのどこかから適応:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

次に、キャストを介してオブジェクトを取得します。

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

        ...
    }
    ...
}
1
orfruit