I'm trying to fetch all ICollection<T> properties from class of unknown type. Also, type T (what the collection is of) is not known at compile time. Firstly I've tried this approach:
foreach (var property in entity.GetType().GetProperties())
{
if (typeof(ICollection).IsAssignableFrom(property.PropertyType) || typeof(ICollection<>).IsAssignableFrom(property.PropertyType))
{
// do something
}
}
but it's not working (evaluating false even for ICollection properties).
I got it working like this:
foreach (var property in entity.GetType().GetProperties())
{
var getMethod = property.GetGetMethod();
var test = getMethod.Invoke(entity, null);
if (test is ICollection)
{
// do something
}
}
but I do not want to execute all getters. Why is the first piece of code not working? How can I find ICollection properties without executing all getters?
It turns out that with IsAssignableFrom check you can't find whether the interface is a derivative of another interface:
Console.WriteLine(typeof(ICollection<>).IsAssignableFrom(typeof(ICollection<Int32>)));
Console.WriteLine(typeof(ICollection<Int32>).IsAssignableFrom(typeof(ICollection<>)));
will both write false;
With little help from here this is the best solution I can come of:
static IEnumerable<PropertyInfo> GetICollectionOrICollectionOfTProperties(this Type type)
{
// Get properties with PropertyType declared as interface
var interfaceProps =
from prop in type.GetProperties()
from interfaceType in prop.PropertyType.GetInterfaces()
where interfaceType.IsGenericType
let baseInterface = interfaceType.GetGenericTypeDefinition()
where (baseInterface == typeof(ICollection<>)) || (baseInterface == typeof(ICollection))
select prop;
// Get properties with PropertyType declared(probably) as solid types.
var nonInterfaceProps =
from prop in type.GetProperties()
where typeof(ICollection).IsAssignableFrom(prop.PropertyType) || typeof(ICollection<>).IsAssignableFrom(prop.PropertyType)
select prop;
// Combine both queries into one resulting
return interfaceProps.Union(nonInterfaceProps);
}
This solution may yield some duplicates(it is hardly possible, but to be sure use Distinct) and it doesn't look very nice.
But it works well on such class with properties with both the interface return types and concrete return types :
class Collections
{
public List<Int32> ListTProp
{
get;
set;
}
public IDictionary<Int32, String> IDictionaryProp
{
get;
set;
}
public ICollection ICollectionProp
{
get;
set;
}
public ICollection<DateTime> IDateTimeCollectionProp
{
get;
set;
}
}
After attempting to use the accepted answer I had a situation where only partial matches where being returned. My object had 3 ICollection<T> properties and I was only being returned with 2. I spent some time testing and trying to figure out why, but I moved on and wrote this:
public static IEnumerable<PropertyInfo> GetICollectionProperties(object entity)
{
return entity.GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType
&& p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>));
}
I have tested with the same test cases and I am getting the correct results returned from this method.
This will not pick up non-generic ICollections, but the OP did ask for ICollection<T>properties, all-though it could be easily re-factored to include. It will also not return properties that are not exactly of type ICollection (ie, Eugene's List and IDictionary in his test case would not be returned (but again, what the OP wanted)).
Related
I'm struggling with implementing the IEquatable<> interface for a class. The class has a Parameter property that uses a generic type. Basically the class definition is like this:
public class MyClass<T> : IEquatable<MyClass<T>>
{
public T Parameter { get; }
...
}
In the Equals() method I'm using EqualityComparer<T>.Default.Equals(Parameter, other.Parameter) to compare the property. Generally, this works fine – as long as the property is not a collection, for example an IEnumerable<T>. The problem is that the default equality comparer for IEnumerable<T> is checking reference equality.
Obviously, you'd want to use SequenceEqual() to compare the IEnumerable<T>. But to get this running, you need to specify the generic type of the SequenceEqual() method. This is the closest I could get:
var parameterType = typeof(T);
var enumerableType = parameterType.GetInterfaces()
.Where(type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(type => type.GetGenericArguments().First()).FirstOrDefault();
if (enumerableType != null)
{
var castedThis = Convert.ChangeType(Parameter, enumerableType);
var castedOther = Convert.ChangeType(other.Parameter, enumerableType);
var isEqual = castedThis.SequenceEqual(castedOther);
}
But this does not work because Convert.ChangeType() returns an object. And of course object does not implement SequenceEqual().
How do I get this working? Thanks for any tipps!
Best regards,
Oliver
Given that you have a generic container that you want to compare various generic items, you don't want to be hard coding in various specific equality checks for certain types. There are going to be lots of situations where the default equality comparison won't work for what some particular caller is trying to do. The comments have numerous different examples of problems that can come up, but also just consider the many many classes out there who's default equality is a reference comparison by for which someone might want a value comparison. You can't have this equality comparer just hard code in a solution for all of those types.
The solution of course is easy. Let the caller provide their own equality implementation, which in C#, means an IEqualityComparer<T>. Your class can become:
public class MyClass<T> : IEquatable<MyClass<T>>
{
private IEqualityComparer<T> comparer;
public MyClass(IEqualityComparer<T> innerComparer = null)
{
comparer = innerComparer ?? EqualityComparer<T>.Default;
}
public T Parameter { get; }
...
}
And now by default the default comparer will be used for any given type, but the caller can always specify a non-default comparer for any type that needs different equality semantics.
Effectively you want a way to say
var castedThis = (IEnumerable<U>)Convert.ChangeType(Parameter, enumerableType);
where T is IEnumerable<U> and U is dynamic.
I don't think you can do that.
If you are happy with some boxing though, you can use the non-generic IEnumerable interface:
public bool Equals(MyClass<T> other)
{
var parameterType = typeof(T);
if (typeof(IEnumerable).IsAssignableFrom(parameterType))
{
var castedThis = ((IEnumerable)this.Parameter).GetEnumerator();
var castedOther = ((IEnumerable)other.Parameter).GetEnumerator();
try
{
while (castedThis.MoveNext())
{
if (!castedOther.MoveNext())
return false;
if (!Convert.Equals(castedThis.Current, castedOther.Current))
return false;
}
return !castedOther.MoveNext();
}
finally
{
(castedThis as IDisposable)?.Dispose();
(castedOther as IDisposable)?.Dispose();
}
}
else
{
return EqualityComparer<T>.Default.Equals(this.Parameter, other.Parameter);
}
}
If you are not happy with the boxing, then you can use reflection to construct and call SequenceEqual (as inspired by How do I invoke an extension method using reflection?):
public bool Equals(MyClass<T> other)
{
var parameterType = typeof(T);
if (typeof(IEnumerable).IsAssignableFrom(parameterType))
{
var enumerableType = parameterType.GetGenericArguments().First();
var sequenceEqualMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(mi => {
if (mi.Name != "SequenceEqual")
return false;
if (mi.GetGenericArguments().Length != 1)
return false;
var pars = mi.GetParameters();
if (pars.Length != 2)
return false;
return pars[0].ParameterType.IsGenericType && pars[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) && pars[1].ParameterType.IsGenericType && pars[1].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>);
})
.First()
.MakeGenericMethod(enumerableType)
;
return (bool)sequenceEqualMethod.Invoke(this.Parameter, new object[] { this.Parameter, other.Parameter });
}
else
{
return EqualityComparer<T>.Default.Equals(this.Parameter, other.Parameter);
}
}
You can cache the sequenceEqualMethod for better performance.
I've been using
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first.DeclaringType == second.DeclaringType && first.Name == second.Name;
To determine whether a reflected property info matches some property I grabbed off a base class.
This approach has begun falling apart when I try to reference properties defined in interfaces.
For example, imagine the following multiple-interface inheritance scenario:
interface IAnimal : { bool IsHungry { get; } }
interface IDog : IAnimal { }
abstract class Animal : IAnimal { public bool IsHungry { get; set; } }
class Dog : Animal, IDog { }
If I'm creating property expressions, all of the following are valid:
Expression<Func<object, bool>> propertyExpression;
propertyExpression = (IAnimal animal) => animal.IsHungry
propertyExpression = (Animal animal) => animal.IsHungry
propertyExpression = (IDog dog) => dog.IsHungry
propertyExpression = (Dog dog) => dog.IsHungry
Since each of these types define or inherit the property IsHungry, all these expressions are valid. One might even argue they are all referring to the same property (although I can appreciate the subtle differences between an interface and instance declaration).
My problem is that I want some way of programatically detecting that all of these properties "come from" the shared interface IAnimal and are compatible. Unfortunately, my test returns false because:
IDog.IsHungry has DeclaringType == typeof(IAnimal) whereas
Dog.IsHungry has DeclaringType == typeof(Animal)
I can't think of an easy way to compare interface and concrete type property expressions without either resorting to a simple Name comparison (which is prone to false-positives) - but I can't think of anything that doesn't involve enumerating all the interfaces inherited by the two types and looking for anything with that property name that's in both sets.
Q: Can we create a function that returns true when comparing any of the PropertyInfo yielded from the above 4 property expressions. (e.g. Detect that they all represent/implement the same base interface property?)
This will probably lead to false positives in the case of the new keyword being used to hide inherited properties, but:
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first == second || // If default equals implementation returns true, no doubt
first.Name == second.Name && (
first.DeclaringType == second.DeclaringType ||
first.DeclaringType.IsAssignableFrom(second.DeclaringType) ||
second.DeclaringType.IsAssignableFrom(first.DeclaringType));
I suppose I could be a bit more specific and check if first.DeclaringType.IsInterface and vice-versa, but it's still possible to implement that interface explicitly and hide its property with a new one with the same name, so the extra check wouldn't make this any 'safer'.
Not sure if I can tell whether one PropertyInfo instance represents the interface implementation of the other.
The solution I came up with compares the getter/setter MethodInfo to the InterfaceMapping. It passes all tests I could think of, but I'm not 100% convinced that there aren't weird corner cases this doesn't catch.
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second)
{
if (first.DeclaringType == second.DeclaringType && first.Name == second.Name)
return true;
bool firstIsSecond = second.DeclaringType.IsAssignableFrom(first.DeclaringType);
bool secondIsFirst = first.DeclaringType.IsAssignableFrom(second.DeclaringType);
if (firstIsSecond || secondIsFirst)
{
PropertyInfo baseProp = firstIsSecond ? second : first;
PropertyInfo derivedProp = firstIsSecond ? first : second;
MethodInfo baseMethod, implMethod;
if (baseProp.GetMethod != null && derivedProp.GetMethod != null)
{
baseMethod = baseProp.GetMethod;
implMethod = derivedProp.GetMethod;
}
else if (baseProp.SetMethod != null && derivedProp.SetMethod != null)
{
baseMethod = baseProp.SetMethod;
implMethod = derivedProp.SetMethod;
}
else
{
return false;
}
// Is it somehow possible to create a situation where both get and set exist
// and the set method to be an implementation while the get method is not?
if (baseMethod.DeclaringType.IsInterface)
return IsInterfaceImplementation(implMethod, baseMethod);
else
return IsOverride(implMethod, baseMethod);
}
return false;
}
private static bool IsInterfaceImplementation(MethodInfo implMethod, MethodInfo interfaceMethod)
{
InterfaceMapping interfaceMap = implMethod.DeclaringType.GetInterfaceMap(interfaceMethod.DeclaringType);
int index = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
// I don't think this can ever be the case?
if (index == -1)
return false;
MethodInfo targetMethod = interfaceMap.TargetMethods[index];
return implMethod == targetMethod || IsOverride(implMethod, targetMethod);
}
private static bool IsOverride(MethodInfo implMethod, MethodInfo baseMethod)
{
return implMethod.GetBaseDefinition() == baseMethod.GetBaseDefinition();
}
I have a class composed of multiple lists and I have generic methods to allow me to do CRUD (and other) operations over those lists.
I'm basically trying to do a variation of DbContext.Set<T> with List.
This is my situation:
public class A
{
private IList<B> Bs;
private IList<C> Cs;
public A()
{
Administrators = new List<B>();
Developers = new List<C>();
}
public void Add<T>(T entity)
{
var propertyIWant = this.GetType().GetProperties().Where(p => p.GetType() == typeof(IList<T>));
var propertyAsList = propertyIWant as List<T>;
propertyAsList.Add(entity);
}
public void Delete<T>(T entity)
{
//Same idea
}
//Other methods
}
The problem is that my code gives me a list of the desired type, but no the actual list (i.e. the property).
So any modifications to that list don't modify the property.
I'd like to be able to do something akin to A.List<T> to get the list of that type (like DbContext can do with DbContext.Set<T>).
You have made a few mistakes here.
Bs and Cs are fields, not properties, so you should use GetFields.
Bs and Cs are private, so you should use the binding flags NonPublic and Instance
The result you get from Where is an IEnumerable<T>. You should call FirstOrDefault or SingleOrDefault to get a single field info.
After getting the field info, you need to call GetValue to get the field's value.
p.GetType() returns typeof(FieldInfo), not the declared type of the field. You should use FieldType instead.
Here is the fixed version:
public void Add<T>(T entity)
{
var fieldIWant = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.FieldType == typeof(IList<T>)).SingleOrDefault();
if (fieldIWant != null) {
var value = (IList<T>)fieldIWant.GetValue(this);
value.Add(entity);
}
}
Or, as thehennyy said in the comments, you should probably look into expression trees, specifically MemberExpressions to do this. Reflection is quite slow.
I have an Interface ISomeThing:
public interface ISomeThing {}
and 2 types implementing it:
public class SomeThing1 : ISomeThing {}
public class SomeThing2 : ISomeThing {}
and then I have a type using those types:
public class FooBar
{
public ICollection<SomeThing1> Foo { get; set; }
public ICollection<SomeThing2> Bar { get; set; }
}
I then have to use reflection to get access to the properties of FooBar:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
The output will be 0. Even if I change the code to look for ICollection<ISomeThing> it won't work:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<ISomeThing>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
I want to get access to the property directly because ICollection brings in Remove etc.. So I need the cast to ICollection<T>.
Edit
I changed my sample to use ISomeThing instead of DateTime. The point is that I don't know the exact generic type at runtime but I need to yield a ICollection<ISomeThing> as the result.
** Edit 2 **
Now I finally come up with my example. This example here shows why I need the cast so badly.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
var value = property.GetValue(instance);
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
ICollection<DateTime> collection = new HashSet<ISomeThing>();
That makes no sense. I assume you meant to type
ICollection<ISomeThing> collection = new HashSet<ISomeThing>();
Moving on.
Console.WriteLine(collection.GetType() as ICollection<ISomeThing>);
This is null because collection.GetType() returns a Type, and a Type does not implement ICollection<ISomething>.
Console.WriteLine(typeof(ICollection<>)
.IsAssignableFrom(collection.GetType()));
This is false because IsAssignableFrom means "can a variable of type ICollection<> be assigned a value of type HashSet<ISomething>. There is no such thing as a variable of type ICollection<>. Generic types have to be constructed before they can be the type of a variable.
I feel almost like the output lies to me when I look at line 1 and 3
It does not.
but I want to get access to the property directly because ICollection brings in Remove etc. So I need the cast to ICollection<T>.
I cannot for the life of me figure out what this sentence means. Can you clarify it?
UPDATE:
Based on the update to the question I now suspect that the actual question is either
Given a Type, I wish to know how many properties are of type ICollection<T> where T is any type that implements ISomeThing"
Easy peasy:
Type type = typeof(FooBar);
var properties = type
.GetProperties()
.Where(p => p.PropertyType.IsGenericType)
.Where(p => p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(p => typeof(ISomeThing).IsAssignableFrom(
p.PropertyType.GenericTypeArguments.Single()))
Console.WriteLine(properties.Count()); // 2
or perhaps it is
Given a Type, I wish to know how many properties are of types that implement ICollection<T> where T is any type that implements ISomeThing"
That would be
var properties = type
.GetProperties()
.Where(p => p.PropertyType
.GetInterfaces()
.Concat(new [] {p.PropertyType})
.Where(i => i.IsGenericType)
.Where(i => i.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(i => typeof(ISomeThing)
.IsAssignableFrom(i.GetGenericArguments().Single()))
.Any());
UPDATE: Based on the latest update to this confusing question, the question is now how to rewrite this loop so that it works:
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
That's easy. You simply don't attempt to cast to ICollection<anything> because you don't need any member of that type in your example. What do we know? That the type is ICollection<T> where T is ISomeThing. It is a requirement that this type implements non-generic IEnumerable and generic IEnumerable<T>.
It is not a requirement that the IEnumerable only yield objects that implement ISomeThing, but the author of the object would be crazy to have IEnumerable yield different objects than could be in the collection, so let's be cautious and stick a type filter on there.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
// Let's emphasize here that we don't know the type by using
// object instead of var
object value = property.GetValue(instance);
// We need to bail if this is null.
if (value == null)
continue;
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var ie = value as IEnumerable;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie.OfType<ISomeThing>())
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
You could also tighten that up a bit with
var q = ie.OfType<ISomeThing>().
.Where(x => x.IsSomeCondition);
foreach (ISomeThing someThingInstance in q)
{
// Do the thing again
}
BONUS EXERCISE
Now, one might reasonable note that ICollection<T> is also convertible to IEnumerable<T>. We could reason like this:
We know we have ICollection<T> for some T that implements ISomeThing.
We know that we can convert ICollection<T> to IEnumerable<T> for any T.
Therefore we can convert this to IEnumerable<T> for some T that implements ISomeThing.
IEnumerable<T> is covariant in T and therefore we can convert our ICollection<T> directly to IEnumerable<ISomeThing> and enumerate that.
Therefore we should really be writing
var ie = value as IEnumerable<ISomeThing>;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie)
The above argument contains a logical flaw. Do you see it?
Give it some thought and once you figure out why this logic is wrong, leave an answer in the comments.
With the updated example, this should get you what you want. It finds all properties that are of ICollection<> and have a generic parameter that implements ISomeThing.
var properties = typeof(FooBar).GetProperties().Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) && p.PropertyType.GenericTypeArguments[0].GetInterfaces().Contains(typeof(ISomeThing)));
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.