Dynamically Access Properties by Type - c#

I'm trying to access a property that is the same type of which is passed into a generic.
Look at the code:
class CustomClass
{
CustomProperty property {get; set;}
}
class CustomProperty
{
}
Main
{
// Create a new instance of my custom class
CustomClass myClass = new CustomClass();
// Create a new instance of another class that is the same type as myClass.property
CustomProperty myProp = new CustomProperty();
// Call the generic method
DynamicallyAccessPropertyOnObject<CustomProperty>(myProp, myClass);
}
private void DynamicallyAccessPropertyOnObject<T>(this T propertyToAccess, CustomClass class)
{
// I want to access the property (In class) that is the same type of that which is passed in the generic (typeof(propertyToAccess))
// TODO: I need help accessing the correct property based on the type passed in
}
If you can't see from the code. Basically I want to be able to pass in a something into a generic and then access the property on a class that is of the same type as the thing that was passed in.
Is there a good way to do this?
If you need clarification let me know...

You can use Reflection, and LINQ:
private static void DynamicallyAccessPropertyOnObject<T>()
{
var customClass = typeof(CustomClass);
var property = customClass
.GetProperties()
.FirstOrDefault(x => x.PropertyType == typeof(T));
}
If you are doing this for CustomClass only, you can remove both parameters.Then you can call it:
DynamicallyAccessPropertyOnObject<CustomProperty>();
If you want to generalize it, use two generic arguments:
private static void DynamicallyAccessPropertyOnObject<T, K>(K targetObj)
{
var targetType = targetObj.GetType();
var property = targetType
.GetProperties()
.FirstOrDefault(x => x.PropertyType == typeof(T));
if(property != null)
{
var value = (T)property.GetValue(targetObj);
}
}
Then call it:
DynamicallyAccessPropertyOnObject<CustomProperty,CustomClass>(myClass);

If there's only one such property you can do:
var prop = typeof(CustomClass).GetProperties().First(p => p.PropertyType == typeof(T));
object value = prop.GetValue(#class, null);
you can set the value with SetValue:
object valueToSet = ...
prop.SetValue(#class, valueToSet);

Related

C# reflection get implementing property of an interface property

I have an interface that I've defined a custom attribute on a property and I want to retrieve the relevant property from a derived instance of that interface.
public interface ITopicProcessor<T>
{
[TopicKey]
string TopicName { get; }
[OtherAttribute]
string OtherProperty { get; }
void Process(T message);
}
public class MyClassProcessor : ITopicProcessor<MyClass>
{
public string TopicName => "MyTopic";
public string OtherProperty => "Irrelevant";
public void Process(MyClass message)
{
}
}
I can get close with the following - the main issue is that the derived interface type doesn't seem to have the same custom attributes as the generic type definition. I'm pretty sure it's partly due to needing to use the underlying method implementation rather than use the property value directly
// iType is typeof(ITopicProcessor<MyClass>)
// I also have access to the generic type definition if need be - i.e. typeof(ITopicProcessor<>)
Func<Type, string> subscriberTypeToTopicKeySelector = iType =>
{
// Creating an instance via a dependency injection framework
var instance = kernel.Get(iType);
var classType = instance.GetType();
var interfaceMap = classType.GetInterfaceMap(iType);
// interfaceMap.InterfaceMethods contains underlying get_property method, but no custom attributes
var interfaceMethod = interfaceMap.InterfaceMethods
.Where(x => x.HasAttribute<TopicKeyAttribute>())
.ToList();
var classMethodInfo = interfaceMap.TargetMethods[Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod)];
return classMethodInfo.Invoke(instance, BindingFlags.Default, null, null, CultureInfo.CurrentCulture)
.ToString();
};
Implementing an interface is not inheriting from a class. This is why such appributes do not propagate from interface to class. See: bradwilson.typepad.com/blog/2011/08/interface-attributes-class-attributes.html
But there are workarounds: Can a C# class inherit attributes from its interface?
Going off #thehennyy's comment I got something that not only works, but can handle the [TopicKey] attribute being applied to either a property or method. For my needs I only want it to appear once in an interface, but anyone else can extend this solution for their needs
subscriberTypeToTopicKeySelector = iType =>
{
var instance = kernel.Get(iType);
var classType = instance.GetType();
var interfaceMap = classType.GetInterfaceMap(iType);
var iTopicKeyPropertyGetMethods = iType.GetProperties()
.Where(x => x.HasAttribute<TopicKeyAttribute>())
.Select(x => x.GetMethod);
var iTopicKeyMethods = iType.GetMethods()
.Where(x => x.HasAttribute<TopicKeyAttribute>())
.Union(iTopicKeyPropertyGetMethods);
var indexOfInterfaceMethod = Array.IndexOf(interfaceMap.InterfaceMethods, iTopicKeyMethods.Single());
var classMethodInfo = interfaceMap.TargetMethods[indexOfInterfaceMethod];
return classMethodInfo.Invoke(instance, BindingFlags.Default, null, null, CultureInfo.CurrentCulture)
.ToString();
};

Using reflection to set an interface member

I'm having trouble setting the value of a property in a class when the property type is and interface using PropertyInfo.SetValue.
This code is for unit testing which I'd like to use to fake out the IDbSets in my Entity.
public static void AddFakeDbSets<T>(this Mock<T> obj) where T : class
{
var iDbSets = typeof(T).GetProperties().Where(p =>
p.PropertyType.IsGenericType == true &&
p.PropertyType.GetGenericTypeDefinition().Name.StartsWith("IDbSet"));
Type baseType = typeof(FakeDbSet<>);
foreach (var iDbSet in iDbSets)
{
if (iDbSet.GetValue(obj.Object) == null)
{
var genericType = baseType.MakeGenericType(iDbSet.PropertyType);
var concreteObject = Activator.CreateInstance(genericType);
iDbSet.SetValue(obj.Object, concreteObject);
}
}
}
Specifically it crashes at iDbSet.SetValue() with the following error:
Object of type
'lnEcommAPI.Tests.FakeDbSet1[System.Data.Entity.IDbSet1[LNWebServicesRepository.WhatsNew]]'
cannot be converted to type
'System.Data.Entity.IDbSet`1[LNWebServicesRepository.WhatsNew]'.
My guess: replace
var genericType = baseType.MakeGenericType(iDbSet.PropertyType);
by
var genericType = baseType.MakeGenericType(iDbSet.PropertyType.GetGenericArguments()[0]);
The problem is that iDbSet.PropertyType is equal to IDbSet<XXX>.
Thus, typeof(FakeDbSet<>).MakeGenericType(iDbSet.PropertyType) creates a FakeDbSet<IDbSet<XXX>>, while you're expecting a FakeDbSet<XXX>

Pass object properties to generate an array of properties names

I have a class with quite a lot of properties and I need to post this class property names to a webservice.
Easy solution is just to create that array by hand, as in new[] {"Id", "Name", "Date", "etc"}.
But that's not fun, I would like to have intellisense support. So far I came up with creating an enum with all these properties, then having a helper function which takes array of these enums and calls .ToString() on each and adds to array.
Problem - quite an useless enum and if my class gets updated, I would need to manualy sync that enum with class properties.
Ideal solution in my mind would be to have something like LINQ extension method, where I could pass properties, something like with Select - ToPropertiesArray(x => {x.Id, X.Name, x.Date})
Am I just crazy and this cannot be done and is just plainly stupid? Or a suggestion on how to pass property names with some kind of IntelliSense support?
public class MyClass
{
public int Id{get;set;}
public string S{get;set;}
public double D{get;set;}
}
public static string[] GetPropsNamesArray<T>(Expression<Func<T,Object>> expr)
{
var t = GetObjectType(expr);
var res = t.GetProperties(BindingFlags.Instance|BindingFlags.Public)
.Select(pi => pi.Name)
.ToArray();
return res;
}
public static Type GetObjectType<T>(Expression<Func<T, object>> expr)
{
if ((expr.Body.NodeType == ExpressionType.Convert) ||
(expr.Body.NodeType == ExpressionType.ConvertChecked))
{
var unary = expr.Body as UnaryExpression;
if (unary != null)
return unary.Operand.Type;
}
return expr.Body.Type;
}
and use:
var selectedPropsNames = GetPropsNamesArray<MyClass>(m => new {m.Id,m.S});
var allPropsNames = GetPropsNamesArray<MyClass>(m => m);
As Lars said, you can use reflection. Using reflection in a method also gets you not having to rewrite when the properties collection changes. The beginning of a sample below iterates the public properties of an entity.
System.Reflection.PropertyInfo[] properties = entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (System.Reflection.PropertyInfo propertyInfo in properties)
{
// ...
}
To have Intellisense support, you can use Expressions:
public static class Helper
{
public static List<string> ToPropertiesArray(params System.Linq.Expressions.Expression<Func<object>>[] exprs)
{
return exprs.Select(expr => ((expr.Body as System.Linq.Expressions.UnaryExpression).Operand
as System.Linq.Expressions.MemberExpression).Member.Name)
.ToList();
}
}
with sample usage:
SomeClass cl = new SomeClass();
var types = Helper.ToPropertiesArray(() => cl.SomeField, () => cl.SomeOtherField);

Casting List<object> to List<T> at runtime

I 'm trying to build a DI container and I 've stumbled on to the following problem: I have a method that retrieves a list of registered instances for a given type and I want to use that to inject IEnumerable<T> properties in a given object. An example of what I am trying to achieve would be the following:
class A { public IList<IExample> Objects { get; set; } }
class B: IExample {}
class C: IExample {}
Container.Register<IExample>(new B());
Container.Register<IExample>(new C());
var obj = new A();
Container.Inject(A);
Debug.Assert(A.Objects != null && A.Objects.Count == 2);
My Retrieve method returns an IList<object>, mainly because I have no type information at that moment, so I am attempting to convert that list into a List<T> at injection time. Here is a succint form of the methods doing the work:
public virtual IList<object> Retrieve(Type type)
{
var instances = Registry[type];
foreach(var instance in instances)
Inject(type, instance); // omitted
return instances;
}
public virtual void Inject<T>(T instance)
{
var properties = typeof (T).GetProperties();
foreach (var propertyInfo in properties)
{
var propertyType = propertyInfo.PropertyType;
if (!IsIEnumerable(propertyType)) continue;
var genericType = propertyType.GetGenericArguments()[0];
propertyInfo.SetValue(instance,
GetListType(genericType, Retrieve(genericType)), null);
}
}
protected virtual object GetListType(Type type, IEnumerable<object> items)
{
return items.Select(item => Convert.ChangeType(item, type)).ToList();
}
The code returns the error: System.InvalidCastException : Object must implement IConvertible. Sadly, I don't know how to proceed from here. Perhaps I am doing this all wrong. I 've thought of using generics or injecting multiple properties by hand, but I'd really like to not have to do that.
Thanks in advance for any help or ideas.
You could create a generic list like this:
public virtual IList Retrieve(Type type)
{
// ...
listType = typeof(List<>).MakeGenericType(new Type[] { type });
IList list = (IList)Activator.CreateInstance(listType);
// ...
return list
}
this list can be casted to IList<T>, because it is one.
You could consider to use IEnumerable and Cast<T>, but then you don't have an instance of a list. I don'^t know how important it is to have one.

Get value from custom attribute-decorated property?

I've written a custom attribute that I use on certain members of a class:
public class Dummy
{
[MyAttribute]
public string Foo { get; set; }
[MyAttribute]
public int Bar { get; set; }
}
I'm able to get the custom attributes from the type and find my specific attribute. What I can't figure out how to do is to get the values of the assigned properties. When I take an instance of Dummy and pass it (as an object) to my method, how can I take the PropertyInfo object I get back from .GetProperties() and get the values assigned to .Foo and .Bar?
EDIT:
My problem is that I can't figure out how to properly call GetValue.
void TestMethod (object o)
{
Type t = o.GetType();
var props = t.GetProperties();
foreach (var prop in props)
{
var propattr = prop.GetCustomAttributes(false);
object attr = (from row in propattr where row.GetType() == typeof(MyAttribute) select row).First();
if (attr == null)
continue;
MyAttribute myattr = (MyAttribute)attr;
var value = prop.GetValue(prop, null);
}
}
However, when I do this, the prop.GetValue call gives me a TargetException - Object does not match target type. How do I structure this call to get this value?
Your need to pass object itself to GetValue, not a property object:
var value = prop.GetValue(o, null);
And one more thing - you should use not .First(), but .FirstOrDefault(), because your code will throw an exception, if some property does not contains any attributes:
object attr = (from row in propattr
where row.GetType() == typeof(MyAttribute)
select row)
.FirstOrDefault();
You get array of PropertyInfo using .GetProperties() and call PropertyInfo.GetValue Method on each
Call it this way:
var value = prop.GetValue(o, null);

Categories