C #에서 리플렉션을 사용하여 문자열에서 속성 값 가져 오기


928

내 코드에서 Reflection 1 예제를 사용하여 데이터 변환을 구현하려고 합니다.

GetSourceValue함수에는 다양한 유형을 비교하는 스위치가 있지만 이러한 유형과 속성을 제거 GetSourceValue하고 단일 문자열 만 매개 변수로 사용하여 속성 값을 가져 오고 싶습니다 . 문자열에 클래스와 속성을 전달하고 속성 값을 확인하고 싶습니다.

이것이 가능한가?

원본 블로그 게시물의 1 웹 보관 버전

답변:


1791
 public static object GetPropValue(object src, string propName)
 {
     return src.GetType().GetProperty(propName).GetValue(src, null);
 }

물론, 당신은 유효성 검사를 추가하고 싶을 것입니다. 그러나 그것은 그 요점입니다.


8
좋고 간단합니다! 나는 그것을 일반적인 것으로 만들 것입니다 :public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider

2
최적화는 다음과 같이 null 예외의 위험을 제거 할 수 있습니다 : " src.GetType().GetProperty(propName)?.GetValue(src, null);";).
shA.t

8
@ shA.t : 나는 그것이 나쁜 생각이라고 생각합니다. 기존 속성의 null 값 또는 속성 없음을 어떻게 구분합니까? 속성 이름이 잘못 전송되었다는 것을 즉시 알고 싶습니다. 이것은 프로덕션 코드는 아니지만보다 구체적인 예외를 던지는 것입니다 (예 : null on GetPropertyand throw PropertyNotFoundException또는 null 인 경우).
Ed S.

210

이런 식으로 어떻습니까 :

public static Object GetPropValue(this Object obj, String name) {
    foreach (String part in name.Split('.')) {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

public static T GetPropValue<T>(this Object obj, String name) {
    Object retval = GetPropValue(obj, name);
    if (retval == null) { return default(T); }

    // throws InvalidCastException if types are incompatible
    return (T) retval;
}

이를 통해 다음과 같이 단일 문자열을 사용하여 속성으로 내려갈 수 있습니다.

DateTime now = DateTime.Now;
int min = GetPropValue<int>(now, "TimeOfDay.Minutes");
int hrs = now.GetPropValue<int>("TimeOfDay.Hours");

이 메소드를 정적 메소드 또는 확장으로 사용할 수 있습니다.


3
@FredJand 당신이 그것을 넘어서 다행! 이 오래된 게시물이 나타날 때 항상 놀라운 일입니다. 약간 모호해서 설명을 위해 약간의 텍스트를 추가했습니다. 또한 이것을 확장 메소드로 사용하도록 전환하고 제네릭 양식을 추가 했으므로 여기에 추가했습니다.
jheddings

왜 위가 아닌 foreach에 널 가드가 있습니까?
산토스

4
@objant는 'obj'가 foreach 루프의 본문에서 재정의되므로 각 반복 중에 확인됩니다.
jheddings

유용하지만 가장자리 속성 중 하나 인 경우 ( 'new'수정자를 사용하여) 숨겨 질 수있는 경우 중복 속성을 찾는 데 예외가 발생합니다. 중첩 된 속성의 속성에 액세스하는 것처럼 마지막 속성 유형을 추적 하고 중첩 된 속성 PropertyInfo.PropertyType대신 사용 하는 것이 더 깔끔합니다 obj.GetType().
Nullius

매직 문자열을 제거하고 이러한 호출에 컴파일 타임 안전을 추가하기 위해 함수를 호출 할 때 name 매개 변수에서 다음 nameof과 같이 C # 6부터 expression을 사용할 수 있습니다 nameof(TimeOfDay.Minutes).
수확

74

추가 Class:

public class Foo
{
    public object this[string propertyName]
    {
        get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
        set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
    }

    public string Bar { get; set; }
}

그런 다음 다음과 같이 사용할 수 있습니다.

Foo f = new Foo();
// Set
f["Bar"] = "asdf";
// Get
string s = (string)f["Bar"];

@EduardoCuomo : 이것으로 리플렉션을 사용하는 것이 가능합니까?
바나나에 우리의 남자

"Bar"가 객체 인 경우이 작업을 수행 할 수 있습니까?
big_water

@big_water SetValueGetValue메소드는 작동합니다 Object. 특정 유형으로 작업해야하는 경우 결과를 GetValue캐스트하고 값을 캐스트하여 값을 지정해야합니다.SetValue
Eduardo Cuomo

@OurManinBananas 죄송합니다. 귀하의 질문을 이해할 수 없습니다. 뭐하고 싶어?
Eduardo Cuomo

이 방법의 이름은 무엇입니까?
Sahan Chinthaka

45

네임 스페이스 ( )를 사용하는 CallByName것은 어떻습니까? 리플렉션을 사용하여 일반 객체, COM 객체 및 동적 객체의 속성, 필드 및 메서드를 가져옵니다.Microsoft.VisualBasicMicrosoft.VisualBasic.dll

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;

그리고

Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();

5
흥미로운 제안, 추가 검사를 통해 필드와 속성, COM 개체를 모두 처리 할 수 있으며 동적 바인딩을 올바르게 처리 할 수도 있음을 증명했습니다 !
IllidanS4는 Monica가

오류가 발생했습니다 : 'MyType'유형의 공용 멤버 'MyPropertyName'을 찾을 수 없습니다.
vldmrrdjcc 2016 년

30

jheddings에 의한 훌륭한 답변. propertyName이 property1.property2 [X] .property3가되도록 집계 된 배열 또는 객체 컬렉션을 참조 할 수 있도록 개선하고 싶습니다.

    public static object GetPropertyValue(object srcobj, string propertyName)
    {
        if (srcobj == null)
            return null;

        object obj = srcobj;

        // Split property name to parts (propertyName could be hierarchical, like obj.subobj.subobj.property
        string[] propertyNameParts = propertyName.Split('.');

        foreach (string propertyNamePart in propertyNameParts)
        {
            if (obj == null)    return null;

            // propertyNamePart could contain reference to specific 
            // element (by index) inside a collection
            if (!propertyNamePart.Contains("["))
            {
                PropertyInfo pi = obj.GetType().GetProperty(propertyNamePart);
                if (pi == null) return null;
                obj = pi.GetValue(obj, null);
            }
            else
            {   // propertyNamePart is areference to specific element 
                // (by index) inside a collection
                // like AggregatedCollection[123]
                //   get collection name and element index
                int indexStart = propertyNamePart.IndexOf("[")+1;
                string collectionPropertyName = propertyNamePart.Substring(0, indexStart-1);
                int collectionElementIndex = Int32.Parse(propertyNamePart.Substring(indexStart, propertyNamePart.Length-indexStart-1));
                //   get collection object
                PropertyInfo pi = obj.GetType().GetProperty(collectionPropertyName);
                if (pi == null) return null;
                object unknownCollection = pi.GetValue(obj, null);
                //   try to process the collection as array
                if (unknownCollection.GetType().IsArray)
                {
                    object[] collectionAsArray = unknownCollection as object[];
                    obj = collectionAsArray[collectionElementIndex];
                }
                else
                {
                    //   try to process the collection as IList
                    System.Collections.IList collectionAsList = unknownCollection as System.Collections.IList;
                    if (collectionAsList != null)
                    {
                        obj = collectionAsList[collectionElementIndex];
                    }
                    else
                    {
                        // ??? Unsupported collection type
                    }
                }
            }
        }

        return obj;
    }

MasterList [0] [1]에 의해 액세스되는 목록의 목록은 어떻습니까?
Jesse Adam

as array-> as object [] 또한 Nullreference 예외가 발생합니다. 나를 위해 작동하는 (가장 효율적인 방법은 아니지만) unknownCollection을 IEnumerable로 캐스팅하고 결과에 ToArray ()를 사용하는 것입니다. 바이올린
Jeroen Jonkman

14

Ed S 의 코드를 사용 하면

보호 수준으로 인해 'ReflectionExtensions.GetProperty (Type, string)'에 액세스 할 수 없습니다

GetProperty()Xamarin.Forms에서 사용할 수없는 것 같습니다 . TargetFrameworkProfile입니다 Profile7내 휴대용 클래스 라이브러리 (.NET 프레임 워크 4.5, 윈도우 8, ASP.NET 코어 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS 클래식)에서.

이제 작동하는 솔루션을 찾았습니다.

using System.Linq;
using System.Reflection;

public static object GetPropValue(object source, string propertyName)
{
    var property = source.GetType().GetRuntimeProperties().FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase));
    return property?.GetValue(source);
}

출처


4
가능한 작은 개선. IF를 교체하고 다음으로 반환 : return property? .GetValue (source);
Tomino

11

중첩 속성 토론에 대해 DataBinder.Eval Method (Object, String)아래와 같이 사용하면 모든 반사 요소를 피할 수 있습니다.

var value = DataBinder.Eval(DateTime.Now, "TimeOfDay.Hours");

물론 System.Web어셈블리에 대한 참조를 추가해야 하지만 이것은 큰 문제는 아닙니다.


8

.NET 표준 (1.6 기준)에서 호출 방법이 변경되었습니다. 또한 C # 6의 null 조건부 연산자를 사용할 수 있습니다.

using System.Reflection; 
public static object GetPropValue(object src, string propName)
{
    return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);
}

1
최대 사용? operator
blfuentes을

4

System.Reflection 네임 스페이스의 PropertyInfo 사용 리플렉션은 우리가 액세스하려는 속성에 관계없이 잘 컴파일됩니다. 런타임 중에 오류가 발생합니다.

    public static object GetObjProperty(object obj, string property)
    {
        Type t = obj.GetType();
        PropertyInfo p = t.GetProperty("Location");
        Point location = (Point)p.GetValue(obj, null);
        return location;
    }

객체의 Location 속성을 얻는 것이 좋습니다.

Label1.Text = GetObjProperty(button1, "Location").ToString();

Location : {X = 71, Y = 27}을 얻습니다. 동일한 방법으로 location.X 또는 location.Y를 반환 할 수도 있습니다.


4
public static List<KeyValuePair<string, string>> GetProperties(object item) //where T : class
    {
        var result = new List<KeyValuePair<string, string>>();
        if (item != null)
        {
            var type = item.GetType();
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var pi in properties)
            {
                var selfValue = type.GetProperty(pi.Name).GetValue(item, null);
                if (selfValue != null)
                {
                    result.Add(new KeyValuePair<string, string>(pi.Name, selfValue.ToString()));
                }
                else
                {
                    result.Add(new KeyValuePair<string, string>(pi.Name, null));
                }
            }
        }
        return result;
    }

이것은 목록에 값이있는 모든 속성을 가져 오는 방법입니다.


왜 이것을하고 있습니까 : type.GetProperty(pi.Name)변수에 == 일 때 pi?
weston

c # 6.0을 사용하는 경우 제거 if하고 selfValue?.ToString()그렇지 않으면 제거 if하고 사용하십시오selfValue==null?null:selfValue.ToString()
weston

또한 목록 List<KeyValuePair<이 이상합니다. 사전을 사용하십시오Dictionary<string, string>
weston

3

다음 코드는 객체 인스턴스에 포함 된 모든 속성 이름 및 값의 전체 계층 구조를 표시하기위한 재귀 적 방법입니다. 이 방법은 GetPropertyValue()이 스레드에서 위 의 간단한 AlexD 답변 버전을 사용합니다 . 이 토론 스레드 덕분에이 작업을 수행하는 방법을 알 수있었습니다!

예를 들어,이 메소드를 사용 WebService하여 다음과 같이 메소드를 호출하여 응답 에서 모든 특성의 폭발 또는 덤프를 표시 합니다.

PropertyValues_byRecursion("Response", response, false);

public static object GetPropertyValue(object srcObj, string propertyName)
{
  if (srcObj == null) 
  {
    return null; 
  }
  PropertyInfo pi = srcObj.GetType().GetProperty(propertyName.Replace("[]", ""));
  if (pi == null)
  {
    return null;
  }
  return pi.GetValue(srcObj);
}

public static void PropertyValues_byRecursion(string parentPath, object parentObj, bool showNullValues)
{
  /// Processes all of the objects contained in the parent object.
  ///   If an object has a Property Value, then the value is written to the Console
  ///   Else if the object is a container, then this method is called recursively
  ///       using the current path and current object as parameters

  // Note:  If you do not want to see null values, set showNullValues = false

  foreach (PropertyInfo pi in parentObj.GetType().GetTypeInfo().GetProperties())
  {
    // Build the current object property's namespace path.  
    // Recursion extends this to be the property's full namespace path.
    string currentPath = parentPath + "." + pi.Name;

    // Get the selected property's value as an object
    object myPropertyValue = GetPropertyValue(parentObj, pi.Name);
    if (myPropertyValue == null)
    {
      // Instance of Property does not exist
      if (showNullValues)
      {
        Console.WriteLine(currentPath + " = null");
        // Note: If you are replacing these Console.Write... methods callback methods,
        //       consider passing DBNull.Value instead of null in any method object parameters.
      }
    }
    else if (myPropertyValue.GetType().IsArray)
    {
      // myPropertyValue is an object instance of an Array of business objects.
      // Initialize an array index variable so we can show NamespacePath[idx] in the results.
      int idx = 0;
      foreach (object business in (Array)myPropertyValue)
      {
        if (business == null)
        {
          // Instance of Property does not exist
          // Not sure if this is possible in this context.
          if (showNullValues)
          {
            Console.WriteLine(currentPath  + "[" + idx.ToString() + "]" + " = null");
          }
        }
        else if (business.GetType().IsArray)
        {
          // myPropertyValue[idx] is another Array!
          // Let recursion process it.
          PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
        }
        else if (business.GetType().IsSealed)
        {
          // Display the Full Property Path and its Value
          Console.WriteLine(currentPath + "[" + idx.ToString() + "] = " + business.ToString());
        }
        else
        {
          // Unsealed Type Properties can contain child objects.
          // Recurse into my property value object to process its properties and child objects.
          PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
        }
        idx++;
      }
    }
    else if (myPropertyValue.GetType().IsSealed)
    {
      // myPropertyValue is a simple value
      Console.WriteLine(currentPath + " = " + myPropertyValue.ToString());
    }
    else
    {
      // Unsealed Type Properties can contain child objects.
      // Recurse into my property value object to process its properties and child objects.
      PropertyValues_byRecursion(currentPath, myPropertyValue, showNullValues);
    }
  }
}

3
public static TValue GetFieldValue<TValue>(this object instance, string name)
{
    var type = instance.GetType(); 
    var field = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.FieldType) && e.Name == name);
    return (TValue)field?.GetValue(instance);
}

public static TValue GetPropertyValue<TValue>(this object instance, string name)
{
    var type = instance.GetType();
    var field = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.PropertyType) && e.Name == name);
    return (TValue)field?.GetValue(instance);
}

3
public class YourClass
{
    //Add below line in your class
    public object this[string propertyName] => GetType().GetProperty(propertyName)?.GetValue(this, null);
    public string SampleProperty { get; set; }
}

//And you can get value of any property like this.
var value = YourClass["SampleProperty"];

3

아래 방법은 나에게 완벽합니다.

class MyClass {
    public string prop1 { set; get; }

    public object this[string propertyName]
    {
        get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
        set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
    }
}

속성 값을 얻으려면

MyClass t1 = new MyClass();
...
string value = t1["prop1"].ToString();

속성 값을 설정하려면

t1["prop1"] = value;

2
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)

2

다음은 중첩 경로를 알려주기 위해 문자열이 필요하지 않은 중첩 속성을 찾는 또 다른 방법입니다. 단일 속성 방법에 대해서는 Ed S.의 공로입니다.

    public static T FindNestedPropertyValue<T, N>(N model, string propName) {
        T retVal = default(T);
        bool found = false;

        PropertyInfo[] properties = typeof(N).GetProperties();

        foreach (PropertyInfo property in properties) {
            var currentProperty = property.GetValue(model, null);

            if (!found) {
                try {
                    retVal = GetPropValue<T>(currentProperty, propName);
                    found = true;
                } catch { }
            }
        }

        if (!found) {
            throw new Exception("Unable to find property: " + propName);
        }

        return retVal;
    }

        public static T GetPropValue<T>(object srcObject, string propName) {
        return (T)srcObject.GetType().GetProperty(propName).GetValue(srcObject, null);
    }

호출 하고 루프에 던져 넣는 대신 Type.GetProperty반환 값 을 확인하는 것이 좋습니다 . nullGetValueNullReferenceException
Groo

2

어떤 객체를 검사하고 있는지 언급 한 적이 없으며 주어진 객체를 참조하는 객체를 거부하기 때문에 정적 객체를 의미한다고 가정합니다.

using System.Reflection;
public object GetPropValue(string prop)
{
    int splitPoint = prop.LastIndexOf('.');
    Type type = Assembly.GetEntryAssembly().GetType(prop.Substring(0, splitPoint));
    object obj = null;
    return type.GetProperty(prop.Substring(splitPoint + 1)).GetValue(obj, null);
}

검사중인 객체를 로컬 변수로 표시했습니다 obj. null정적 의미, 그렇지 않으면 원하는 것으로 설정하십시오. 또한 GetEntryAssembly()"실행 중"어셈블리를 얻는 데 사용할 수있는 몇 가지 방법 중 하나입니다. 유형을로드하는 데 어려움이있는 경우이 방법을 사용하는 것이 좋습니다.


2

Heleonix.Reflection 라이브러리를 살펴보십시오 . 경로별로 멤버를 가져 오거나 설정 / 호출하거나 리플렉션보다 빠른 게터 / 세터 (대리자로 컴파일 된 람다)를 작성할 수 있습니다. 예를 들면 다음과 같습니다.

var success = Reflector.Get(DateTime.Now, null, "Date.Year", out int value);

또는 getter를 한 번 작성하고 재사용을 위해 캐시하십시오 (이는 성능이 우수하지만 중간 멤버가 널인 경우 NullReferenceException을 발생시킬 수 있음).

var getter = Reflector.CreateGetter<DateTime, int>("Date.Year", typeof(DateTime));
getter(DateTime.Now);

또는 List<Action<object, object>>다른 게터 를 만들려면 컴파일 된 대리자의 기본 형식을 지정하십시오 (유형 변환은 컴파일 된 람다에 추가됨).

var getter = Reflector.CreateGetter<object, object>("Date.Year", typeof(DateTime));
getter(DateTime.Now);

1
자신의 코드에서 5-10 줄로 적절한 시간에 구현할 수 있다면 타사 라이브러리를 사용하지 마십시오.
Artem G

1

더 짧은 방법 ....

var a = new Test { Id = 1 , Name = "A" , date = DateTime.Now};
var b = new Test { Id = 1 , Name = "AXXX", date = DateTime.Now };

var compare = string.Join("",a.GetType().GetProperties().Select(x => x.GetValue(a)).ToArray())==
              string.Join("",b.GetType().GetProperties().Select(x => x.GetValue(b)).ToArray());

1

jheddingsAlexD는 모두 속성 문자열을 해결하는 방법에 대한 훌륭한 답을 썼다. 그 목적을 위해 전용 라이브러리를 작성했기 때문에 믹스에 내 것을 던지고 싶습니다.

Pather.CSharp 의 주요 클래스는Resolver입니다. 기본적으로 속성, 배열 및 사전 항목을 확인할 수 있습니다.

예를 들어 이런 물체가 있다면

var o = new { Property1 = new { Property2 = "value" } };

을 얻고 싶다면 Property2다음과 같이 할 수 있습니다.

IResolver resolver = new Resolver();
var path = "Property1.Property2";
object result = r.Resolve(o, path); 
//=> "value"

이것이 해결할 수있는 가장 기본적인 예입니다. 다른 기능이나 확장 방법을 보려면 Github 페이지로 이동하십시오 .


0

여기 내 해결책이 있습니다. 또한 COM 개체와 함께 작동하며 COM 개체의 컬렉션 / 배열 항목에 액세스 할 수 있습니다.

public static object GetPropValue(this object obj, string name)
{
    foreach (string part in name.Split('.'))
    {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        if (type.Name == "__ComObject")
        {
            if (part.Contains('['))
            {
                string partWithoundIndex = part;
                int index = ParseIndexFromPropertyName(ref partWithoundIndex);
                obj = Versioned.CallByName(obj, partWithoundIndex, CallType.Get, index);
            }
            else
            {
                obj = Versioned.CallByName(obj, part, CallType.Get);
            }
        }
        else
        {
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }
            obj = info.GetValue(obj, null);
        }
    }
    return obj;
}

private static int ParseIndexFromPropertyName(ref string name)
{
    int index = -1;
    int s = name.IndexOf('[') + 1;
    int e = name.IndexOf(']');
    if (e < s)
    {
        throw new ArgumentException();
    }
    string tmp = name.Substring(s, e - s);
    index = Convert.ToInt32(tmp);
    name = name.Substring(0, s - 1);
    return index;
}

0

다른 답변을 바탕으로 얻은 내용은 다음과 같습니다. 오류 처리에 대해 구체적으로 설명하는 데 약간의 어려움이 있습니다.

public static T GetPropertyValue<T>(object sourceInstance, string targetPropertyName, bool throwExceptionIfNotExists = false)
{
    string errorMsg = null;

    try
    {
        if (sourceInstance == null || string.IsNullOrWhiteSpace(targetPropertyName))
        {
            errorMsg = $"Source object is null or property name is null or whitespace. '{targetPropertyName}'";
            Log.Warn(errorMsg);

            if (throwExceptionIfNotExists)
                throw new ArgumentException(errorMsg);
            else
                return default(T);
        }

        Type returnType = typeof(T);
        Type sourceType = sourceInstance.GetType();

        PropertyInfo propertyInfo = sourceType.GetProperty(targetPropertyName, returnType);
        if (propertyInfo == null)
        {
            errorMsg = $"Property name '{targetPropertyName}' of type '{returnType}' not found for source object of type '{sourceType}'";
            Log.Warn(errorMsg);

            if (throwExceptionIfNotExists)
                throw new ArgumentException(errorMsg);
            else
                return default(T);
        }

        return (T)propertyInfo.GetValue(sourceInstance, null);
    }
    catch(Exception ex)
    {
        errorMsg = $"Problem getting property name '{targetPropertyName}' from source instance.";
        Log.Error(errorMsg, ex);

        if (throwExceptionIfNotExists)
            throw;
    }

    return default(T);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.