[C#] C #에서 익명 유형의 속성에 액세스하는 방법은 무엇입니까?

나는 이것을 가지고있다:

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

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

… 그리고 익명 개체의 “Checked”속성을 가져올 수 있는지 궁금합니다. 이것이 가능한지 확실하지 않습니다. 이것을 시도했습니다.

if (nodes.Any(n => n["Checked"] == false)) …하지만 작동하지 않습니다.

감사



답변

강력한 형식의 익명 형식 목록이 필요한 경우 목록도 익명 형식으로 만들어야합니다. 가장 쉬운 방법은 배열과 같은 시퀀스를 목록에 투영하는 것입니다.

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

그러면 다음과 같이 액세스 할 수 있습니다.

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

컴파일러가 작동하는 방식으로 인해 익명 유형이 동일한 구조를 가지므로 동일한 유형이되기 때문에 목록을 만든 후에는 다음도 작동해야합니다. 그래도 이것을 확인할 컴파일러가 없습니다.

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


답변

객체를 type으로 저장하는 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);

를 사용 ?.하면 결과 vnull세 가지 다른 상황에있게됩니다!

  1. o이다 null, 그래서 객체는 전혀 없다
  2. onull아니지만 속성이 없습니다.Foo
  3. o속성이 Foo있지만 실제 값은입니다 null.

따라서 이것은 이전 예제와 동일하지 않지만 세 가지 경우를 모두 동일하게 처리하려는 경우 의미가있을 수 있습니다.


답변

Reflection을 사용하여 익명 유형의 속성을 반복 할 수 있습니다. “Checked”속성이 있는지 확인하고 값이 있는지 확인합니다.

이 블로그 게시물을 참조하십시오.
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!");
    }
}


답변

허용되는 답변은 목록을 선언하는 방법을 올바르게 설명하며 대부분의 시나리오에 적극 권장됩니다.

그러나 나는 질문을 다루는 다른 시나리오를 발견했습니다. 당신은 같은 기존 개체 목록을 사용하려면 어떻게이있는 경우 ViewData["htmlAttributes"]MVC ? 속성에 어떻게 액세스 할 수 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!");

참고 : 이것은 후기 바인딩을 사용 합니다. 즉, 객체에 Checked속성 이없는 경우 런타임에만 인식 RuntimeBinderException하고이 경우 a 를 throw합니다 . 따라서 존재 하지 않는 속성 을 사용하려고 Checked2하면 다음 메시지가 표시 됩니다. 런타임 : "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. 리플렉션 솔루션

리플렉션이있는 솔루션은 이전 및 새 C # 컴파일러 버전 모두에서 작동 합니다 . 이전 C # 버전의 경우이 답변 끝에있는 힌트를 참조하십시오.

배경

시작점으로 여기 에서 좋은 답을 찾았 습니다 . 아이디어는 리플렉션을 사용하여 익명 데이터 유형을 사전으로 변환하는 것입니다. 사전을 사용하면 속성 이름이 키로 저장되므로 속성에 쉽게 액세스 할 수 있습니다 (처럼 액세스 할 수 있음 myDict["myProperty"]).

위의 링크에서 코드에 의해 영감을, 내가 제공하는 확장 클래스를 생성 GetProp, UnanonymizePropertiesUnanonymizeListItems확장 방법, 익명의 속성을 단순화 액세스있다. 이 클래스를 사용하면 다음과 같이 간단히 쿼리를 수행 할 수 있습니다.

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"];

참고 : 모든 속성을 포함 할 필요는없는 개체 목록이 있고 (예 : 일부는 “Checked”속성을 포함하지 않음) “Checked”값을 기반으로 쿼리를 작성하려는 경우 다음을 수행 할 수 있습니다. 이 작업을 수행:

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

이것은 KeyNotFoundException“Checked”속성이 존재하지 않는 경우 발생 하는 것을 방지 합니다.


아래 클래스에는 다음 확장 메서드가 포함되어 있습니다.

  • 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 이후 사용 가능한 연산자를 – 만약 당신이 나왔습니다 작업 이전의 C # 컴파일러 (예를 들어, C # 3.0), 간단하게 교체 ?..?[에 의해 [예를 들어, 모든 곳

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

이전 C # 컴파일러를 강제로 사용 하지 않는 경우 null 조건을 사용하면 null 처리가 훨씬 쉬워 지므로 그대로 유지하세요.

참고 : 동적을 사용하는 다른 솔루션과 마찬가지로이 솔루션도 후기 바인딩을 사용하지만이 경우 예외가 발생하지 않습니다. 존재하지 않는 속성을 참조하는 경우 널 조건부 연산자 를 유지하면 됩니다.

일부 애플리케이션에 유용 할 수있는 것은 속성이 솔루션 2의 문자열을 통해 참조되므로 매개 변수화 할 수 있다는 것입니다.


답변

최근에 .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;

        ...
    }
    ...
}


답변