Get property value from string using reflection
Asked Answered
S

25

1193

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

Seaver answered 28/7, 2009 at 21:58 Comment(0)
A
2247
 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.

Antoniaantonie answered 28/7, 2009 at 22:2 Comment(4)
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); }Stenopetalous
An optimization can be remove risk of null exception like this: "src.GetType().GetProperty(propName)?.GetValue(src, null);" ;).Trow
@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.)Antoniaantonie
Just in case your property really is a Field and not a Property (like mine ;)) then use GetField instead of GetPropertyAlta
M
257

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.

Muscovite answered 23/12, 2009 at 18:55 Comment(5)
@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.Muscovite
Why is the null guard in the foreach and not above?Avignon
@Avignon since 'obj' is redefined in the body of the foreach loop, it is checked during each iteration.Muscovite
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.Looper
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.Exhilarative
G
119

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"];
Greggs answered 23/7, 2014 at 19:58 Comment(7)
@EduardoCuomo: Is it possible to use reflection with this so you don't need to know what members the class has?Fenner
Is it possible to do this if "Bar" were an object?Durrell
@Durrell the SetValue and GetValue methods works with Object. If you need to work with a specific type, you should cast the result of GetValue and cast the value to assign it with SetValueGreggs
Sorry @OurManinBananas, I can't understand your question. What do you want to do?Greggs
What is name of this type methods..?Colure
@SahanChinthaka indexed property. learn.microsoft.com/tr-tr/dotnet/csharp/programming-guide/…Kerenkeresan
Thanks a lot, I put this on some my classesTuft
A
48

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();
Aerify answered 23/12, 2009 at 19:24 Comment(2)
Interesting suggestion, further inspection proved that it can handle both fields and properties, COM objects, and it can even handle correctly dynamic binding!Zacatecas
I am getting an error: Public member 'MyPropertyName' on type 'MyType' not found.Clique
M
46

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;
    }
Moira answered 14/2, 2012 at 9:18 Comment(2)
what about a List of Lists accessed by MasterList[0][1] ?Tarsal
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. fiddleBilk
H
20

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

Hypso answered 6/9, 2016 at 9:18 Comment(1)
Just a tiny possible improvement. Replace IF and next return by: return property?.GetValue(source);Unwise
B
14

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;
Burnsides answered 9/7, 2018 at 5:19 Comment(1)
This a fantastic answer super simply to implement & use. Are there any potential unanticipated side effects from using this?Vociferance
I
13

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);
}
Inhibitor answered 24/5, 2017 at 15:6 Comment(1)
up for using the ? operatorCredendum
T
12

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.

Tutorial answered 29/7, 2015 at 14:19 Comment(0)
C
6
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.

Chiller answered 6/1, 2016 at 13:46 Comment(3)
Why are doing this: type.GetProperty(pi.Name) when that is == to the variable pi?Hydraulic
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()Hydraulic
Also a list of List<KeyValuePair< is odd, use a dictionary Dictionary<string, string>Hydraulic
S
5

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.

Stratocumulus answered 4/11, 2013 at 6:6 Comment(0)
E
5
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"];
Extra answered 6/3, 2018 at 15:6 Comment(0)
S
3

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);
    }
  }
}
Slattern answered 26/2, 2015 at 2:4 Comment(0)
H
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);
}
Hollah answered 29/9, 2017 at 16:22 Comment(0)
S
2

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.

Stepp answered 23/12, 2009 at 19:13 Comment(0)
S
2
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)
Shelah answered 13/6, 2010 at 21:12 Comment(0)
S
2

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);
    }
Solo answered 5/11, 2014 at 0:15 Comment(1)
It might be better to check if Type.GetProperty returns null instead of calling GetValue and having NullReferenceExceptions thrown in a loop.Chafe
M
2

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);
Modify answered 13/7, 2018 at 10:57 Comment(1)
never use 3rd party libs, if you can implement it in your own code in a reasonable time in 5-10 lines.Transact
A
2

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 :)

Ashford answered 6/1, 2022 at 15:17 Comment(1)
Missing semicolon on var m = line?Moiramoirai
L
1

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());
Lardy answered 13/7, 2014 at 4:11 Comment(0)
F
1

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.

Filet answered 26/8, 2016 at 21:48 Comment(0)
R
1

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);
}
Rawlins answered 26/4, 2019 at 19:23 Comment(0)
A
0

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;
}
Airwaves answered 24/2, 2018 at 14:24 Comment(0)
G
0

Whenever you want to loop over all properties in on an object and then use each value of the property must use this piece of code:

foreach (var property in request.GetType().GetProperties())
{
    var valueOfProperty = property.GetValue(properties, null);
}
Griff answered 11/12, 2022 at 6:42 Comment(0)
G
0
    public static object GetPropValue(object src, string propName)
{
    try
    {
        // Split the nested object 
        var path = propName.Split(new[] { '.' }, 2);

        if (path.Length > 1)
        {
            return GetPropValue(src.GetType().GetProperty(path[0]).GetValue(src, null), path[1]);
        }
        else
            return src.GetType().GetProperty(propName).GetValue(src, null);
    }
    catch (Exception ex)
    {
        //If the path is wrong
        return null;
    }            

}

}

Gocart answered 31/12, 2023 at 14:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.