ChatGPT解决这个技术问题 Extra ChatGPT

Get property value from string using reflection

I am trying implement the Data transformation using Reflection1 example in my code.

The GetSourceValue function has a switch comparing various types, but I want to remove these types and properties and have GetSourceValue get the value of the property using only a single string as the parameter. I want to pass a class and property in the string and resolve the value of the property.

Is this possible?

1 Web Archive version of original blog post


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

Of course, you will want to add validation and whatnot, but that is the gist of it.


Nice and simple! I'd make it generic though: public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
An optimization can be remove risk of null exception like this: "src.GetType().GetProperty(propName)?.GetValue(src, null);" ;).
@shA.t: I think that's a bad idea. How do you differentiate between a null value of an existing property or no property at all? I'd much rather know immediately that I was sending in a bad property name. This is not production code, but a better improvement would be to throw a more specific exception (e.g. check for null on GetProperty and throw PropertyNotFoundException or something if null.)
Just in case your property really is a Field and not a Property (like mine ;)) then use GetField instead of GetProperty
j
jheddings

How about something like this:

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;
}

This will allow you to descend into properties using a single string, like this:

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

You can either use these methods as static methods or extensions.


@FredJand glad you stumbled on it! It always surprising when these old posts turn up. It was a little vague so I added a bit of text to explain it. I have also switched to using these as extension methods and added a generics form, so I added it here.
Why is the null guard in the foreach and not above?
@Santhos since 'obj' is redefined in the body of the foreach loop, it is checked during each iteration.
Useful, but in the edge case that one of the nested properties might be hidden (using the 'new' modifier), it will throw an exception for finding duplicate properties. It would be neater to keep track of the last property type and use PropertyInfo.PropertyType instead of obj.GetType() on nested properties, just like accessing the property on a nested property would.
You can use nameof expression as of C#6 like this: nameof(TimeOfDay.Minutes) on the name parameter when calling the function to rid magic strings and add compile time safety to these calls.
E
Eduardo Cuomo

Add to any 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; }
}

Then, you can use as:

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

@EduardoCuomo: Is it possible to use reflection with this so you don't need to know what members the class has?
Is it possible to do this if "Bar" were an object?
What is name of this type methods..?
Thanks a lot, I put this on some my classes
J
J0e3gan

What about using the CallByName of the Microsoft.VisualBasic namespace (Microsoft.VisualBasic.dll)? It uses reflection to get properties, fields, and methods of normal objects, COM objects, and even dynamic objects.

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

and then

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

Interesting suggestion, further inspection proved that it can handle both fields and properties, COM objects, and it can even handle correctly dynamic binding!
I am getting an error: Public member 'MyPropertyName' on type 'MyType' not found.
M
MarredCheese

Great answer by jheddings. I would like to improve it by allowing referencing of aggregated arrays or collections of objects, so that propertyName could be 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;
    }

what about a List of Lists accessed by MasterList[0][1] ?
as Array -> as object[] also results in Nullreference exception. What works for me (prop not most efficient method), is to cast unknownCollection to IEnumerable and than use ToArray() on the result. fiddle
t
testing

If I use the code from Ed S. I get

'ReflectionExtensions.GetProperty(Type, string)' is inaccessible due to its protection level

It seems that GetProperty() is not available in Xamarin.Forms. TargetFrameworkProfile is Profile7 in my Portable Class Library (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).

Now I found a working solution:

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);
}

Source


Just a tiny possible improvement. Replace IF and next return by: return property?.GetValue(source);
R
Rubens Farias

About the nested properties discussion, you can avoid all the reflection stuff if you use the DataBinder.Eval Method (Object, String) as below:

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

Of course, you'll need to add a reference to the System.Web assembly, but this probably isn't a big deal.


M
Matt Frear

The method to call has changed in .NET Standard (as of 1.6). Also we can use C# 6's null conditional operator.

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

up for using the ? operator
O
OneCricketeer

The below method works perfect for me:

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); }
    }
}

To get the property value:

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

To set the property value:

t1["prop1"] = value;

B
Boncho Valkov
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;
    }

This is a way to get all properties with their values in a List.


Why are doing this: type.GetProperty(pi.Name) when that is == to the variable pi?
If you are using c# 6.0, get rid of if and do selfValue?.ToString() Otherwise get rid of if and use selfValue==null?null:selfValue.ToString()
Also a list of List<KeyValuePair< is odd, use a dictionary Dictionary<string, string>
t
tom redfern

Using PropertyInfo of the System.Reflection namespace. Reflection compiles just fine no matter what property we try to access. The error will come up during run-time.

    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;
    }

It works fine to get the Location property of an object

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

We'll get the Location : {X=71,Y=27} We can also return location.X or location.Y on the same way.


I
Irshad

The following code is a Recursive method for displaying the entire hierarchy of all of the Property Names and Values contained in an object's instance. This method uses a simplified version of AlexD's GetPropertyValue() answer above in this thread. Thanks to this discussion thread, I was able to figure out how to do this!

For example, I use this method to show an explosion or dump of all of the properties in a WebService response by calling the method as follows:

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);
    }
  }
}

R
Rahma Samaroon
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);
}

K
Komal Narang
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"];

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

R
Recursor

Here is another way to find a nested property that doesn't require the string to tell you the nesting path. Credit to Ed S. for the single property method.

    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);
    }

It might be better to check if Type.GetProperty returns null instead of calling GetValue and having NullReferenceExceptions thrown in a loop.
I
Irshad

You never mention what object you are inspecting, and since you are rejecting ones that reference a given object, I will assume you mean a static one.

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);
}

Note that I marked the object that is being inspected with the local variable obj. null means static, otherwise set it to what you want. Also note that the GetEntryAssembly() is one of a few available methods to get the "running" assembly, you may want to play around with it if you are having a hard time loading the type.


H
Hennadii Lutsyshyn

Have a look at the Heleonix.Reflection library. You can get/set/invoke members by paths, or create a getter/setter (lambda compiled into a delegate) which is faster than reflection. For example:

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

Or create a getter once and cache for reuse (this is more performant but might throw NullReferenceException if an intermediate member is null):

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

Or if you want to create a List<Action<object, object>> of different getters, just specify base types for compiled delegates (type conversions will be added into compiled lambdas):

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

never use 3rd party libs, if you can implement it in your own code in a reasonable time in 5-10 lines.
p
pasx

Although the original question was about how to get the value of the property using only a single string as the parameter, it makes a lot of sense here to use an Expression rather than simply a string to ensure that the caller never uses a hard coded property name. Here is a one line version with usage:

public static class Utils
...
    public static TVal GetPropertyValue<T, TVal>(T t, Expression<Func<T, TVal>> x)
        => (TVal)((x.Body as MemberExpression)?.Member as PropertyInfo)!.GetValue(t);

...
    var val = Utils.GetPropertyValue(foo,  p => p.Bar);

Here is a slightly better version in terms of readability a error handling:

public static TVal GetPropertyValue<T, TVal>(T t, Expression<Func<T, TVal>> x)
{
    var m = (x.Body as MemberExpression)?.Member
    var p = m as PropertyInfo;

    if (null == p)
        throw new ArgumentException($"Unknown property: {typeof(T).Name}.{(m?.Name??"???")}");

    return (TVal)p.GetValue(t);
}

In short you pass in a lambda expression reading a property. The body of the lambda - the part on the right of the fat arrow - is a member expression from which you can get the member name and which you can cast to a PropertyInfo, provided the member is actually a Property and not, for instance, a method.

In the short version, the null forgiving operator - the ! in the expression - tells the compiler that the PropertyInfo will not be null. This is a big lie and you will get a NullReferenceException at runtime. The longer version gives you the name of the property if it manages to get it.

PS: Thanks to Oleg G. for the initial version of this code :)


a
andrewsi

shorter way ....

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());

C
Community

jheddings and AlexD both wrote excellent answers on how to resolve property strings. I'd like to throw mine in the mix, since I wrote a dedicated library exactly for that purpose.

Pather.CSharp's main class is Resolver. Per default it can resolve properties, array and dictionary entries.

So, for example, if you have an object like this

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

and want to get Property2, you can do it like this:

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

This is the most basic example of the paths it can resolve. If you want to see what else it can, or how you can extend it, just head to its Github page.


J
Jeff Codes

Here's what I got based on other answers. A little overkill on getting so specific with the error handling.

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);
}

u
user3175253

Here is my solution. It works also with COM objects and allows to access collection/array items from COM objects.

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;
}