Get indexed value from object over reflection - c#

I have the problem, that i don't know how to call the this property of an object instance over reflection.
I have the following method:
public object GetValue(SOP sop, object value)
{
// 1. find the this getter/setter method for the value parameter
// 2. Invoke the method and pass the result of GetIndexArray(sop) as a parameter
// 3. return the result of the method
}
which should call the this property of the value-instance and return the value the property returns.
The problem is, that i don't know the type of the value-instance, which is passed as a parameter.
The index which should be passed to the this property is given over the following method:
private object[] GetIndexArray(SOP sop)
But i don't know how to call the this-property. The object-instance can be anything, a string, Dictionary, ...
Does any one has an idea, how to solve the problem?
EDIT:
The GetValue method should do the follwing task, but dynamically over reflection:
public object GetValue(SOP sop, object value)
{
// Non relfection, lets say 'value' is a Dicitionary<string, string>
// return value["key"];
// The type of 'value' is unknown, and the index-parameter (the keys) are accessable over GetIndexArray()
}
EDIT2:
In c# every getter and setter can be invoked via reflection as a method. Is there a way, to get the methods for the "this" property? If it's possible, the problem can be solved with invoking the mesthods.
Call of the GetValue method
object pInst = parentInstance.GetType().GetProperty(VariableName, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).GetValue(parentInstance, null);
var indexChldren = FindChildrenOfType<IndexNode>();
if (indexChldren.Count > 0)
{
pInst = indexChldren[0].GetValue(sop, pInst);
}

I decided to remake the post, since my previous one didn't correctly do what it was supposed to do.
To check which indexers are on an object, you can use the following, which will
Return null when object is null
Return an empty IList when no indexers are found
Return a list of MethodInfos with the found indexers
The trick to check it, is to check if a property has any GetIndexParameters();
public IList<MethodInfo> GetIndexProperties(object obj)
{
if (obj == null)
{
return null;
}
var type = obj.GetType();
IList<MethodInfo> results = new List<MethodInfo>();
try
{
var props = type.GetProperties(System.Reflection.BindingFlags.Default |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);
if (props != null)
{
foreach (var prop in props)
{
var indexParameters = prop.GetIndexParameters();
if (indexParameters == null || indexParameters.Length == 0)
{
continue;
}
var getMethod = prop.GetGetMethod();
if (getMethod == null)
{
continue;
}
results.Add(getMethod);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return results;
}
This works with a List, Dictionary, string, object with indexers, indexers defined with IndexerNameAttribute
As an example, i used this MainMethod, to try several possibilities
object[] exampleHayStack = new object[] {
"This is a test of an indexer",
new TestIndexer(),
null,
string.Empty,
new ClassIndexer(),
new Dictionary<string, string>() { { "key", "value" } },
new List<string>() { "A", "B", "C", "D", "E", "F", "G" } };
ClassIndexer myIndexer = new ClassIndexer();
foreach (var obj in exampleHayStack)
{
var methods = myIndexer.GetIndexProperties(obj);
if (methods == null || methods.Count == 0)
{
Console.WriteLine("{0} doesn't have any indexers", obj);
continue;
}
Console.WriteLine("Testing {0}", obj);
foreach (MethodInfo mi in methods)
{
IList<object> indexParams = new List<object>();
var requiredParams = mi.GetParameters();
foreach (var par in requiredParams)
{
indexParams.Add(myIndexer.ParamForObject(obj, par));
}
try
{
var result = mi.Invoke(obj, indexParams.ToArray());
Console.WriteLine("Result of requesting ({0}) = {1}", string.Join(",", indexParams), result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.ReadLine();
Which then results into:
Testing This is a test of an indexer
Result of requesting (21) = i
Testing TestReflection.Program+TestIndexer
Result of requesting (53) = 5
Result of requesting (Key) = Key item
doesn't have any indexers
Testing
Exception has been thrown by the target of an invocation.
TestReflection.Program+ClassIndexer doesn't have any indexers
Testing System.Collections.Generic.Dictionary`2[System.String,System.String]
Result of requesting (key) = value
Testing System.Collections.Generic.List`1[System.String]
Result of requesting (5) = F
The complete implementation of the ClassIndexer you can find here, it contains an extra method for getting possible key values (but those you have already)
public class ClassIndexer
{
Random pNext = new Random();
public IList<MethodInfo> GetIndexProperties(object obj)
{
if (obj == null)
{
return null;
}
var type = obj.GetType();
IList<MethodInfo> results = new List<MethodInfo>();
try
{
var props = type.GetProperties(System.Reflection.BindingFlags.Default |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);
if (props != null)
{
foreach (var prop in props)
{
var indexParameters = prop.GetIndexParameters();
if (indexParameters == null || indexParameters.Length == 0)
{
continue;
}
var getMethod = prop.GetGetMethod();
if (getMethod == null)
{
continue;
}
results.Add(getMethod);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return results;
}
public object ParamForObject(object obj, ParameterInfo pi)
{
if (obj is IDictionary)
{
int maxNumber = ((IDictionary)obj).Keys.Count;
if (pi.ParameterType.Equals(typeof(int)))
{
return pNext.Next(maxNumber);
}
if (pi.ParameterType.Equals(typeof(string)))
{
int target = pNext.Next(maxNumber);
foreach (var key in ((IDictionary)obj).Keys)
{
target--;
if (target <= 0)
{
return key;
}
}
return null;
}
}
if (obj is string)
{
if (pi.ParameterType.Equals(typeof(int)))
{
return pNext.Next((obj as string).Length);
}
}
if (obj is IList)
{
return pNext.Next(((IList)obj).Count);
}
if (pi.ParameterType.Equals(typeof(string)))
{
return "Key";
}
if (pi.ParameterType.Equals(typeof(int)))
{
return pNext.Next(100);
}
return null;
}
public ClassIndexer()
{
}
}
For the rest, this was a good research, thanks for the question !

Related

get the value of the attributes in C#

this is a very simple question.
such as this code:
if(o == null)
{
o = new { };
}
PropertyInfo[] p1 = o.GetType().GetProperties();
foreach(PropertyInfo pi in p1)
{}
but like this:
ModelA.ModelB.ModelC.ModelD.ModelE
how to get ModelE's value by reflect ModelA
There is a solution explained here:
using a helper method:
public static class ReflectionHelper
{
public static Object GetPropValue(this Object obj, String propName)
{
string[] nameParts = propName.Split('.');
if (nameParts.Length == 1)
{
return obj.GetType().GetProperty(propName).GetValue(obj, null);
}
foreach (String part in nameParts)
{
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;
}
}
then the method can be used like this:
ModelA obj = new ModelA { */....*/ };
obj.GetPropValue("modelB.modelC.modelD.modelE");
please note that you should pass the property names to the function not the class names.
using nested function to do it like following:
var testObj = new
{
nameA = "A",
ModelB = new
{
nameB = "B",
ModelC = new
{
NameC = "C",
}
}
};
var result = ParseProperty(testObj, null, "ModelA");
public Dictionary<string, object> ParseProperty(object o, Dictionary<string, object> result, string preFix = null)
{
result = result ?? new Dictionary<string, object>();
if (o == null) return result;
Type t = o.GetType();
//primitive type or value type or string or nested return
if (t.IsPrimitive || t.IsValueType || t.FullName == "System.String" || t.IsNested) return result;
var proerties = o.GetType().GetProperties();
foreach (var property in proerties)
{
var value = property.GetValue(o);
result.Add($"{preFix}.{property.Name}", value);
//nested call
ParseProperty(value, result, $"{preFix}.{property.Name}");
}
return result;
}
I assume these are all anonymous types because otherwise you could just do GetProperties() on the type of ModelE.
So you basically have to next 5 of your loops like
foreach (PropertyInfo pi1 in o1.GetType().GetProperties())
{
if (pi.Name = "ModelB") // or some other criterion
{
o2 = pi1.GetValue(o1);
foreach (PropertyInfo pi2 in o2.GetType().GetProperties())
{
if (pi.Name = "ModelC") // or some other criterion
{
o3 = pi1.GetValue(o2);
// and so on
}
}
}
}

Can a DynamicParameter rely on the values of other DynamicParameters?

I have a GetDynamicParameters() on cmdlet Get-DateSlain that does something like this:
public object GetDynamicParameters()
{
List<string> houseList = {"Stark", "Lannister", "Tully"};
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a house name",
},
new ValidateSetAttribute(houseList.ToArray()),
};
if (!this.ContainsKey("House"))
{
this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
}
}
And this works as expected - users can type Get-DateSlain -House, and tab through the available houses. However, once a house is chosen, I want to be able to narrow down the results to characters in that house. Furthermore, if it's house 'Stark', I want to allow a -Wolf parameter. So to implement (some value validity checks removed for brevity):
public object GetDynamicParameters()
{
if (this.runtimeParameters.ContainsKey("House"))
{
// We already have this key - no need to re-add. However, now we can add other parameters
var house = this.runtimeParameters["House"].Value.ToString();
if (house == "Stark")
{
List<string> characters = { "Ned", "Arya", "Rob" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a character name",
},
new ValidateSetAttribute(characters.ToArray()),
};
this.runtimeParameters.Add("Character", new RuntimeDefinedParameter("Character", typeof(string), attributes));
List<string> wolves = { "Shaggydog", "Snow", "Lady" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a wolf name",
},
new ValidateSetAttribute(wolves.ToArray()),
};
this.runtimeParameters.Add("Wolf", new RuntimeDefinedParameter("Wolf", typeof(string), attributes));
}
else if (house == "Lannister")
{
List<string> characters = { "Jaimie", "Cersei", "Tywin" };
// ...
}
// ...
return this.runtimeParameters;
}
List<string> houseList = {"Stark", "Lannister", "Tully"};
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a house name",
},
new ValidateSetAttribute(houseList.ToArray()),
};
this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
}
This looks like it should work, but it doesn't. The GetDynamicParameters function is only called once, and that is before a value is supplied to this.runtimeParameters["House"]. Since it doesn't re-evaluate after that value is filled in, the additional field(s) are never added, and any logic in ProcessRecord that relies on these fields will fail.
So - is there a way to have multiple dynamic parameters that rely on each other?
Have a look a the aswer to this question, it shows a way to access the values of other dynamic parameters in the GetDynamicParameters method:
Powershell module: Dynamic mandatory hierarchical parameters
I adapted the code from the mentioned answer so it can handle SwitchParameters and the raw input parameter is converted to the actual type of the cmdlet parameter. It does not work if the dynamic parameter you want to get the value for is passed via pipeline. I think that is not possible because dynamic parameters are always created before pipeline input is evaluated. Here it is:
public static class DynamicParameterExtension
{
public static T GetUnboundValue<T>(this PSCmdlet cmdlet, string paramName, int unnamedPosition = -1))
{
var context = TryGetProperty(cmdlet, "Context");
var processor = TryGetProperty(context, "CurrentCommandProcessor");
var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;
if (args != null)
{
var isSwitch = typeof(SwitchParameter) == typeof(T);
var currentParameterName = string.Empty;
object unnamedValue = null;
var i = 0;
foreach (var arg in args)
{
var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
if (isParameterName != null && true.Equals(isParameterName))
{
var parameterName = TryGetProperty(arg, "ParameterName") as string;
currentParameterName = parameterName;
if (isSwitch && string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
{
return (T)(object)new SwitchParameter(true);
}
continue;
}
var parameterValue = TryGetProperty(arg, "ArgumentValue");
if (currentParameterName != string.Empty)
{
if (string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
{
return ConvertParameter<T>(parameterValue);
}
}
else if (i++ == unnamedPosition)
{
unnamedValue = parameterValue;
}
currentParameterName = string.Empty;
}
if (unnamedValue != null)
{
return ConvertParameter<T>(unnamedValue);
}
}
return default(T);
}
static T ConvertParameter<T>(this object value)
{
if (value == null || Equals(value, default(T)))
{
return default(T);
}
var psObject = value as PSObject;
if (psObject != null)
{
return psObject.BaseObject.ConvertParameter<T>();
}
if (value is T)
{
return (T)value;
}
var constructorInfo = typeof(T).GetConstructor(new[] { value.GetType() });
if (constructorInfo != null)
{
return (T)constructorInfo.Invoke(new[] { value });
}
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (Exception)
{
return default(T);
}
}
static object TryGetProperty(object instance, string fieldName)
{
if (instance == null || string.IsNullOrEmpty(fieldName))
{
return null;
}
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);
try
{
if (propertyInfo != null)
{
return propertyInfo.GetValue(instance, null);
}
var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);
return fieldInfo?.GetValue(instance);
}
catch (Exception)
{
return null;
}
}
}
So for your example you should be able to use it like:
public object GetDynamicParameters()
{
var houseList = new List<string> { "Stark", "Lannister", "Tully" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute { HelpMessage = "Enter a house name" },
new ValidateSetAttribute(houseList.ToArray()),
};
var runtimeParameters = new RuntimeDefinedParameterDictionary
{
{"House", new RuntimeDefinedParameter("House", typeof (string), attributes)}
};
var selectedHouse = this.GetUnboundValue<string>("House");
//... add parameters dependant on value of selectedHouse
return runtimeParameters;
}
After all I'm not sure if it's a good idea trying to get those dynamic parameter values in the first place. It is obviously not supported by PowerShell Cmdlet API (see all the reflection to access private members in the GetUnboundValue method), you have to reimplement the PowerShell parameter conversion magic (see ConvertParameter, I'm sure I missed some cases there) and there is the restriction with pipelined values. Usage at your own risk:)

Generic Return Type

I would like to write a class which provides me decrypted values for any given domain model. The following logic works fine, but don't know what to declare in the place of XXXXXX.
Sample Domain Model:
public class emp
{
public string empname{get;set;}
}
I will call the DecryptByObject method something like:
var x = DecryptByObject(emp,KEY);
DecryptByObject Method:
public XXXXX DecryptByObject(XXXXXX myObject, string decryptKey)
{
Type t = myObject.GetType();
foreach (MemberInfo mi in t.GetMembers())
{
try
{
if (mi.MemberType == MemberTypes.Property)
{
string value = ((PropertyInfo)mi).GetValue(myObject).ToString();
var bytes = Convert.FromBase64String(value);
var decryValue = MachineKey.Unprotect(bytes, decryptKey);
((PropertyInfo)mi).SetValue(myObject, Encoding.UTF8.GetString(decryValue));
}
}
catch (Exception ex) { }
}
return myObject;
}
You should use like as below.
public T DecryptByObject<T>(T myObject, string decryptKey)
{
Type t = myObject.GetType();
PropertyInfo prop = t.GetProperty("Items");
object list = prop.GetValue(myObject);
foreach (MemberInfo mi in t.GetMembers())
{
try
{
if (mi.MemberType == MemberTypes.Property)
{
string value = ((PropertyInfo)mi).GetValue(myObject).ToString();
var bytes = Convert.FromBase64String(value);
var decryValue = MachineKey.Unprotect(bytes, decryptKey);
((FieldInfo)mi).SetValue(myObject, Encoding.UTF8.GetString(decryValue));
}
}
catch (Exception ex) { }
}
return myObject;
}
Try this:
public T DecryptByObject<T>(T myObject, string decryptKey)
As a side point, you want to add some error checking here as prop could well be null or an error may be thrown:
Type t = myObject.GetType();
PropertyInfo prop = t.GetProperty("Items");
if (prop == null)
{
// handle this error...
}
object list = prop.GetValue(myObject);

Please explain why the object reference lost in my code

Hi I am doing something related to Reflection, I don't understand what's wrong with my code. I try to clean up my codes however, the first piece of code will not update my instance values, when I step through the debugger I can see the correct result from "newobj", however the "next" reference is lost as a result of not updating my instance values. The only change I have done is to add "this" to queue, to me it is no difference. Can someone explain the reason behind this?
private void UpdateBreathFirst()// This code is WRONG!!! but why?
{
RootQueue = new Queue<object>();
RootQueue.Enqueue(this);
while (RootQueue.Count > 0)
{
var next = RootQueue.Dequeue();
EnqueueChildren(next);
var newobj = next.GetType().GetMethod("Get").Invoke(next, null);
ValueAssign(next, newobj);
}
}
private void UpdateBreathFirst()//This code produces correct result.
{
RootQueue = new Queue<object>();
var val = GetType().GetMethod("Get").Invoke(this, null);
ValueAssign(this, val);
EnqueueChildren(this);
while (RootQueue.Count > 0)
{
var next = RootQueue.Dequeue();
EnqueueChildren(next);
var newobj = next.GetType().GetMethod("Get").Invoke(next, null);
ValueAssign(next, newobj);
}
}
Other support codes
private Queue<object> RootQueue;
private void EnqueueChildren(object obj)
{
if (BaseTypeCompare(obj.GetType(), typeof(SerializedEntity<>)))
{
foreach (var propertyInfo in obj.GetType().GetProperties())
{
if (BaseTypeCompare(propertyInfo.PropertyType, typeof (List<>)))
{
var list = (IList) propertyInfo.GetValue(obj, null);
if (list != null)
{
foreach (object item in list)
{
RootQueue.Enqueue(item);
}
}
}
}
}
}
public static void ValueAssign(object a, object b)
{
foreach (var p in a.GetType().GetProperties())
{
foreach (var p2 in b.GetType().GetProperties())
{
if (p.Name == p2.Name && BaseTypeCompare(p.GetType(), p2.GetType()))
{
p.SetValue(a, p2.GetValue(b, null), null);
}
}
}
}
public static bool BaseTypeCompare(Type t, Type t2)
{
if (t.FullName.StartsWith(t2.FullName)) return true;
if (t == typeof(object)) return false;
return BaseTypeCompare(t.BaseType, t2);
}
I think I found my problem myself, my ValueAssign() has some bug. I found a similar method on the net which works perfectly!
private static void CopyObject(object sourceObject, ref object destObject)
{
// If either the source, or destination is null, return
if (sourceObject == null || destObject == null)
return;
// Get the type of each object
Type sourceType = sourceObject.GetType();
Type targetType = destObject.GetType();
// Loop through the source properties
foreach (PropertyInfo p in sourceType.GetProperties())
{
// Get the matching property in the destination object
PropertyInfo targetObj = targetType.GetProperty(p.Name);
// If there is none, skip
if (targetObj == null)
continue;
// Set the value in the destination
targetObj.SetValue(destObject, p.GetValue(sourceObject, null), null);
}

Using reflection in C# to get properties of a nested object

Given the following objects:
public class Customer {
public String Name { get; set; }
public String Address { get; set; }
}
public class Invoice {
public String ID { get; set; }
public DateTime Date { get; set; }
public Customer BillTo { get; set; }
}
I'd like to use reflection to go through the Invoice to get the Name property of a Customer. Here's what I'm after, assuming this code would work:
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);
Of course, this fails since "BillTo.Address" is not a valid property of the Invoice class.
So, I tried writing a method to split the string into pieces on the period, and walk the objects looking for the final value I was interested in. It works okay, but I'm not entirely comfortable with it:
public Object GetPropValue(String name, Object obj) {
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;
}
Any ideas on how to improve this method, or a better way to solve this problem?
EDIT after posting, I saw a few related posts... There doesn't seem to be an answer that specifically addresses this question, however. Also, I'd still like the feedback on my implementation.
I use following method to get the values from (nested classes) properties like
"Property"
"Address.Street"
"Address.Country.Name"
public static object GetPropertyValue(object src, string propName)
{
if (src == null) throw new ArgumentException("Value cannot be null.", "src");
if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");
if(propName.Contains("."))//complex type nested
{
var temp = propName.Split(new char[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
}
else
{
var prop = src.GetType().GetProperty(propName);
return prop != null ? prop.GetValue(src, null) : null;
}
}
Here is the Fiddle: https://dotnetfiddle.net/PvKRH0
I know I'm a bit late to the party, and as others said, your implementation is fine
...for simple use cases.
However, I've developed a library that solves exactly that use case, Pather.CSharp.
It is also available as Nuget Package.
Its main class is Resolver with its Resolve method.
You pass it an object and the property path, and it will return the desired value.
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Address");
But it can also resolve more complex property paths, including array and dictionary access.
So, for example, if your Customer had multiple addresses
public class Customer {
public String Name { get; set; }
public IEnumerable<String> Addresses { get; set; }
}
you could access the second one using Addresses[1].
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Addresses[1]");
I actually think your logic is fine. Personally, I would probably change it around so you pass the object as the first parameter (which is more inline with PropertyInfo.GetValue, so less surprising).
I also would probably call it something more like GetNestedPropertyValue, to make it obvious that it searches down the property stack.
You have to access the ACTUAL object that you need to use reflection on. Here is what I mean:
Instead of this:
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);
Do this (edited based on comment):
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo");
Customer cust = (Customer)info.GetValue(inv, null);
PropertyInfo info2 = cust.GetType().GetProperty("Address");
Object val = info2.GetValue(cust, null);
Look at this post for more information:
Using reflection to set a property of a property of an object
In hopes of not sounding too late to the party, I would like to add my solution:
Definitely use recursion in this situation
public static Object GetPropValue(String name, object obj, Type type)
{
var parts = name.Split('.').ToList();
var currentPart = parts[0];
PropertyInfo info = type.GetProperty(currentPart);
if (info == null) { return null; }
if (name.IndexOf(".") > -1)
{
parts.Remove(currentPart);
return GetPropValue(String.Join(".", parts), info.GetValue(obj, null), info.PropertyType);
} else
{
return info.GetValue(obj, null).ToString();
}
}
You don't explain the source of your "discomfort," but your code basically looks sound to me.
The only thing I'd question is the error handling. You return null if the code tries to traverse through a null reference or if the property name doesn't exist. This hides errors: it's hard to know whether it returned null because there's no BillTo customer, or because you misspelled it "BilTo.Address"... or because there is a BillTo customer, and its Address is null! I'd let the method crash and burn in these cases -- just let the exception escape (or maybe wrap it in a friendlier one).
Here is another implementation that will skip a nested property if it is an enumerator and continue deeper. Properties of type string are not affected by the Enumeration Check.
public static class ReflectionMethods
{
public static bool IsNonStringEnumerable(this PropertyInfo pi)
{
return pi != null && pi.PropertyType.IsNonStringEnumerable();
}
public static bool IsNonStringEnumerable(this object instance)
{
return instance != null && instance.GetType().IsNonStringEnumerable();
}
public static bool IsNonStringEnumerable(this Type type)
{
if (type == null || type == typeof(string))
return false;
return typeof(IEnumerable).IsAssignableFrom(type);
}
public static Object GetPropValue(String name, Object obj)
{
foreach (String part in name.Split('.'))
{
if (obj == null) { return null; }
if (obj.IsNonStringEnumerable())
{
var toEnumerable = (IEnumerable)obj;
var iterator = toEnumerable.GetEnumerator();
if (!iterator.MoveNext())
{
return null;
}
obj = iterator.Current;
}
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
}
based on this question and on
How to know if a PropertyInfo is a collection
by Berryl
I use this in a MVC project to dynamically Order my data by simply passing the Property to sort by
Example:
result = result.OrderBy((s) =>
{
return ReflectionMethods.GetPropValue("BookingItems.EventId", s);
}).ToList();
where BookingItems is a list of objects.
> Get Nest properties e.g., Developer.Project.Name
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName)
{
if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
if (PropertName.Split('.').Length == 1)
return t.GetType().GetProperty(PropertName);
else
return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
}
if (info == null) { /* throw exception instead*/ }
I would actually throw an exception if they request a property that doesn't exist. The way you have it coded, if I call GetPropValue and it returns null, I don't know if that means the property didn't exist, or the property did exist but it's value was null.
public static string GetObjectPropertyValue(object obj, string propertyName)
{
bool propertyHasDot = propertyName.IndexOf(".") > -1;
string firstPartBeforeDot;
string nextParts = "";
if (!propertyHasDot)
firstPartBeforeDot = propertyName.ToLower();
else
{
firstPartBeforeDot = propertyName.Substring(0, propertyName.IndexOf(".")).ToLower();
nextParts = propertyName.Substring(propertyName.IndexOf(".") + 1);
}
foreach (var property in obj.GetType().GetProperties())
if (property.Name.ToLower() == firstPartBeforeDot)
if (!propertyHasDot)
if (property.GetValue(obj, null) != null)
return property.GetValue(obj, null).ToString();
else
return DefaultValue(property.GetValue(obj, null), propertyName).ToString();
else
return GetObjectPropertyValue(property.GetValue(obj, null), nextParts);
throw new Exception("Property '" + propertyName.ToString() + "' not found in object '" + obj.ToString() + "'");
}
I wanted to share my solution although it may be too late. This solution is primarily to check if the nested property exists. But it can be easily tweaked to return the property value if needed.
private static PropertyInfo _GetPropertyInfo(Type type, string propertyName)
{
//***
//*** Check if the property name is a complex nested type
//***
if (propertyName.Contains("."))
{
//***
//*** Get the first property name of the complex type
//***
var tempPropertyName = propertyName.Split(".", 2);
//***
//*** Check if the property exists in the type
//***
var prop = _GetPropertyInfo(type, tempPropertyName[0]);
if (prop != null)
{
//***
//*** Drill down to check if the nested property exists in the complex type
//***
return _GetPropertyInfo(prop.PropertyType, tempPropertyName[1]);
}
else
{
return null;
}
}
else
{
return type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
}
}
I had to refer to few posts to come up with this solution. I think this will work for multiple nested property types.
My internet connection was down when I need to solve the same problem, so I had to 're-invent the wheel':
static object GetPropertyValue(Object fromObject, string propertyName)
{
Type objectType = fromObject.GetType();
PropertyInfo propInfo = objectType.GetProperty(propertyName);
if (propInfo == null && propertyName.Contains('.'))
{
string firstProp = propertyName.Substring(0, propertyName.IndexOf('.'));
propInfo = objectType.GetProperty(firstProp);
if (propInfo == null)//property name is invalid
{
throw new ArgumentException(String.Format("Property {0} is not a valid property of {1}.", firstProp, fromObject.GetType().ToString()));
}
return GetPropertyValue(propInfo.GetValue(fromObject, null), propertyName.Substring(propertyName.IndexOf('.') + 1));
}
else
{
return propInfo.GetValue(fromObject, null);
}
}
Pretty sure this solves the problem for any string you use for property name, regardless of extent of nesting, as long as everything's a property.
Based on the original code from #jheddings, I have created a extension method version with generic type and verifications:
public static T GetPropertyValue<T>(this object sourceObject, string propertyName)
{
if (sourceObject == null) throw new ArgumentNullException(nameof(sourceObject));
if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException(nameof(propertyName));
foreach (string currentPropertyName in propertyName.Split('.'))
{
if (string.IsNullOrWhiteSpace(currentPropertyName)) throw new InvalidOperationException($"Invalid property '{propertyName}'");
PropertyInfo propertyInfo = sourceObject.GetType().GetProperty(currentPropertyName);
if (propertyInfo == null) throw new InvalidOperationException($"Property '{currentPropertyName}' not found");
sourceObject = propertyInfo.GetValue(sourceObject);
}
return sourceObject is T result ? result : default;
}
I wrote a method that received one object type as the argument from the input and returns dictionary<string,string>
public static Dictionary<string, string> GetProperties(Type placeHolderType)
{
var result = new Dictionary<string, string>();
var properties = placeHolderType.GetProperties();
foreach (var propertyInfo in properties)
{
string name = propertyInfo.Name;
string description = GetDescriptionTitle(propertyInfo);
if (IsNonString(propertyInfo.PropertyType))
{
var list = GetProperties(propertyInfo.PropertyType);
foreach (var item in list)
{
result.Add($"{propertyInfo.PropertyType.Name}_{item.Key}", item.Value);
}
}
else
{
result.Add(name, description);
}
}
return result;
}
public static bool IsNonString(Type type)
{
if (type == null || type == typeof(string))
return false;
return typeof(IPlaceHolder).IsAssignableFrom(type);
}
private static string GetDescriptionTitle(MemberInfo memberInfo)
{
if (Attribute.GetCustomAttribute(memberInfo, typeof(DescriptionAttribute)) is DescriptionAttribute descriptionAttribute)
{
return descriptionAttribute.Description;
}
return memberInfo.Name;
}
public static object GetPropertyValue(object src, string propName)
{
if (src == null) throw new ArgumentException("Value cannot be null.", "src");
if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");
var prop = src.GetType().GetProperty(propName);
if (prop != null)
{
return prop.GetValue(src, null);
}
else
{
var props = src.GetType().GetProperties();
foreach (var property in props)
{
var propInfo = src.GetType().GetProperty(property.Name);
if (propInfo != null)
{
var propVal = propInfo.GetValue(src, null);
if (src.GetType().GetProperty(property.Name).PropertyType.IsClass)
{
return GetPropertyValue(propVal, propName);
}
return propVal;
}
}
return null;
}
usage: calling part
var emp = new Employee() { Person = new Person() { FirstName = "Ashwani" } };
var val = GetPropertyValue(emp, "FirstName");
above can search the property value at any level
Try inv.GetType().GetProperty("BillTo+Address");

Categories