可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
public class Address
{
public string ZipCode {get; set;}
}
public class Customer
{
public Address Address {get; set;}
}
how can I access eitther "ZipCode" or "Address.ZipCode" with reflection? For example:
Typeof(Customer).GetProperty("ZipCode")?
回答1:
You'd need something like:
PropertyInfo addressProperty = typeof(Customer).GetProperty("Address");
ProportyInfo zipCodeProperty = addressProperty.PropertyType.GetProperty("ZipCode");
object address = addressProperty.GetValue(customer, null);
object zipCode = zipCodeProperty.GetValue(address, null);
Basically if you want to take a string "Address.ZipCode" and navigate down it, you need to split it by "." and then call GetProperty on the appropriate type at every step to get the property itself, then PropertyInfo.GetValue to get the next value in the chain. Something like this:
public static object FollowPropertyPath(object value, string path)
{
Type currentType = value.GetType();
foreach (string propertyName in path.Split('.'))
{
PropertyInfo property = currentType.GetProperty(propertyName);
value = property.GetValue(value, null);
currentType = property.PropertyType;
}
return value;
}
Call it like this:
object zipCode = FollowPropertyPath(customer, "Address.ZipCode");
Note that this works on the compile-time types of the properties. If you want it to cope with the execution time type (e.g. if customer.Address didn't have a ZipCode property, but the actual type returned by Address did) then change property.PropertyType
to property.GetType()
.
Also note that this doesn't have any error handling etc :)
回答2:
Jon Skeet's answer is fine, I had to extend his method a bit though, in order to account for derived instances within the property path:
public static class ReflectorUtil
{
public static object FollowPropertyPath(object value, string path)
{
if (value == null) throw new ArgumentNullException("value");
if (path == null) throw new ArgumentNullException("path");
Type currentType = value.GetType();
object obj = value;
foreach (string propertyName in path.Split('.'))
{
if (currentType != null)
{
PropertyInfo property = null;
int brackStart = propertyName.IndexOf("[");
int brackEnd = propertyName.IndexOf("]");
property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName);
obj = property.GetValue(obj, null);
if (brackStart > 0)
{
string index = propertyName.Substring(brackStart + 1, brackEnd - brackStart - 1);
foreach (Type iType in obj.GetType().GetInterfaces())
{
if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
obj = typeof(ReflectorUtil).GetMethod("GetDictionaryElement")
.MakeGenericMethod(iType.GetGenericArguments())
.Invoke(null, new object[] { obj, index });
break;
}
if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IList<>))
{
obj = typeof(ReflectorUtil).GetMethod("GetListElement")
.MakeGenericMethod(iType.GetGenericArguments())
.Invoke(null, new object[] { obj, index });
break;
}
}
}
currentType = obj != null ? obj.GetType() : null; //property.PropertyType;
}
else return null;
}
return obj;
}
public static TValue GetDictionaryElement<TKey, TValue>(IDictionary<TKey, TValue> dict, object index)
{
TKey key = (TKey)Convert.ChangeType(index, typeof(TKey), null);
return dict[key];
}
public static T GetListElement<T>(IList<T> list, object index)
{
return list[Convert.ToInt32(index)];
}
}
Using property.PropertyType will get you the property type defined on the obj class, while using obj.GetType() will get you the actual type of the property's instance.
EDIT: @Oliver - you're absolutely right, thanks for noting that. I adjusted the method to allow for generic Lists and Dictionaries. While I don't like the parsing part, I used Marc Gravell's clever idea in this thread to get the indexer property's values.
回答3:
The existing answers are fine; just an alternative perspective: in many scenarios it is desirable to use System.ComponentModel rather than direct reflection, as this allows for runtime property scenarios - i.e. how a DataTable's DataView exposes the columns as properties.
Performance wise - by default this is largely identical, but if you are doing lots of this (for example, bulk data import/export), you can actually get significant performance increases using this approach, courtesy of HyperDescriptor.
To use System.ComponentModel, the code is similar, but subtly different:
static void Main()
{
object obj = new Customer { Address = new Address { ZipCode = "abcdef" } };
object address = GetValue(obj, "Address");
object zip = GetValue(address, "ZipCode");
Console.WriteLine(zip);
}
static object GetValue(object component, string propertyName)
{
return TypeDescriptor.GetProperties(component)[propertyName].GetValue(component);
}
This then gives you the same handling as though you had used data-binding to bind to "Address.ZipCode" (glossing over some details like lists etc).
(note that you could cast zip as string etc if you know that is the expected type)
To get the value from a deep path (including the same list handling that data-binding uses), you would use something like:
static object ResolveValue(object component, string path) {
foreach(string segment in path.Split('.')) {
if (component == null) return null;
if(component is IListSource) {
component = ((IListSource)component).GetList();
}
if (component is IList) {
component = ((IList)component)[0];
}
component = GetValue(component, segment);
}
return component;
}
The list stuff roughly mirrors the behaviour of regular data-binding (although it omits a few things like binding-contexts, currency-managers, etc)
回答4:
typeof (Customer).GetProperty("Address").PropertyType.GetProperty("ZipCode")
回答5:
adabyron,
I created a version of your code for when you only need to grab the types, and if you don't have an actual object instance.
public static Type FollowPropertyPath<T>(string path)
{
if (path == null) throw new ArgumentNullException("path");
Type currentType = typeof(T);
foreach (string propertyName in path.Split('.'))
{
int brackStart = propertyName.IndexOf("[");
var property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName);
if (property == null)
return null;
currentType = property.PropertyType;
if (brackStart > 0)
{
foreach (Type iType in currentType.GetInterfaces())
{
if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (IDictionary<,>))
{
currentType = iType.GetGenericArguments()[1];
break;
}
if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (ICollection<>))
{
currentType = iType.GetGenericArguments()[0];
break;
}
}
}
}
return currentType;
}
回答6:
Problem: Weakly Typed Variables:
@jonskeet's FollowPropertyPath(...) method almost met my needs exactly; except that
my property was weakly typed; therefore, currentType = property.PropertyType
simply returned System.Object and failed on the next iteration of the foreach-loop.
Solution:
To use the runtime type rather than the design-time type, I adjusted the method as follows:
public static object FollowPropertyPath(object value, string path)
{
Type currentType = value.GetType();
foreach (string propertyName in path.Split('.'))
{
PropertyInfo property = currentType.GetProperty(propertyName);
value = property.GetValue(value, null);
currentType = value.GetType(); // <-- Change
}
return value;
}