730

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

21 답변


1453

 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.


  • You asked in your question "I want pass class", I assume that meant "object" and not "class" since "class" makes little sense. Why would you vote this down? Can you not figure out how to modify this code to use "this" instead? Is it a static property of a class? You need to be more specific then, this is the correct answer. - Ed S.
  • src.GetType().GetProperty(propName).GetValue(src, null) returns null for the nested objects. I mean like School.Employee.Name in this case school.GetType().GetProperty(Employee.Name).GetValue(school, null) - Murali Murugesan
  • @Murali: Because that's wrong. You would need to get the value of the Employee property first and then do the same thing once more. - Ed S.
  • @TomášZato: It's ok, the downvote is no biggie, just be sure you know what you're talking about before handing out downvotes in the future. - Ed S.
  • 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); } - Ohad Schneider

179

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. - jheddings
  • Why is the null guard in the foreach and not above? - Santhos
  • @Santhos since 'obj' is redefined in the body of the foreach loop, it is checked during each iteration. - jheddings

50

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? - Our Man in Bananas
  • Is it possible to do this if "Bar" were an object? - big_water
  • Make the object like a dictionary with a string key!!! awesome - tim
  • @big_water 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 SetValue - Eduardo Cuomo
  • Sorry @OurManinBananas, I can't understand your question. What do you want to do? - Eduardo Cuomo

41

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! - IllidanS4
  • this should be the correct answer of all :) - Hoang Minh

25

Great answer by jheddings. I would like to improve it 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 Array[];
                    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] ? - Jesse Adam

10

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

7

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.


6

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



4

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.


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

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? - weston
  • 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() - weston
  • Also a list of List<KeyValuePair< is odd, use a dictionary Dictionary<string, string> - weston

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


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

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.


2

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


1

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


2

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


2

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


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


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.


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


0

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;


0

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. - Artem G

Linked


Related

Latest