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.
Related
How would you assign a list to a generic list since they are not the same type.
If I have a generic list:
List<T> myList = new List<T>();
and I have another list
List<OtherType> otherList = new List<OtherType>();
After I fill otherList with values. What are ways I can assign otherList to the generic list? Preferably without using a foreach.
if they are the same type you can do a basic type conversion
if(typeof(T) == typeof(OtherType))
myList = otherList as List<T>;
But that would make no sense, so I'd imagine you need some kind of conversion, problem is we need to specify that T is assignable from your base class
public static class StaticFoo
{
public static List<T> Foo<T>() where T : class
{
List<MyOtherClass> returnList = new List<MyOtherClass>() { new MyOtherClass() };
if(typeof(T).IsAssignableFrom(typeof(MyOtherClass)))
return returnList.Select(x => x as T).ToList();
throw new Exception($"Cannot convert {typeof(T)} to MyOtherClass");
}
}
public class MyClass { }
public class MyOtherClass : MyClass { }
The above code will work if you call it with T = MyClass or any other class that myOtherClass can be cast to. Alternatively you might want a concrete conversion method for a set of predefined types, it's kind of hacky but you could do something like this
public static class StaticFoo
{
public static List<T> Foo<T>() where T : class
{
List<MyOtherClass> returnList = new List<MyOtherClass>() { new MyOtherClass() };
return returnList.Select(x => x.Convert(typeof(T)) as T).ToList();
}
}
public class MyOtherClass {
public object Convert(Type type) {
if (type == typeof(string)) //more if statements for more types
return this.ToString(); //just an example
throw new NotImplementedException($"No cast available for type {type}");
}
}
Some context for the relationship between the generic type and your concrete class would be helpful
edit:
some advice that ignores your actual question. Most likely, you want to create an interface and return a list of that interface (I'm assuming that will match your use case more closely). Alternatively just change the signature to return List< object> - then you can do
return otherList.ToList<object>();
List<T> is invariant, so you can only assign lists of the same type. The closest you can come is creating a new list with the same items.
List<T> list = otherList.Select( x => (T)x ).ToList();
I'm having huge problems with my code responsible for loading data from a database and converting it to lists of my custom data model objects.
As there are many data tables I'm going to have many Lists and I don't really want to create and assign them manually using copy/paste and just modifying the types.
Here is what I have for now, but there are errors displayed inside the LoadAllModelListsAsProperties method about failing conversions between Type and generic type parameters.
I can't figure out how get around this incompatibility of Type and <T>. What should I do?
public class DataProvider
{
private Dictionary<Type, object> Memory { get; set; }
private static Dictionary<Type, Type> AllModelTypes { get; }
static DataProvider()
{
AllModelTypes = new Dictionary<Type, Type>()
{
{ typeof(AreaModel), typeof(Areas) },
{ typeof(GroupModel), typeof(Groups) },
/* many more.... */
{ typeof(TownModel), typeof(Towns) }
};
}
public DataProvider()
{
// fill Memory dictionary with empty list instances
foreach (Type ModelType in AllModelTypes.Keys)
{
Memory.Add(ModelType, Activator.CreateInstance(
typeof(List<>).MakeGenericType(ModelType)));
}
}
public List<TModel> GetModelList<TModel>(Type modelType)
where TModel : ModelBase
{
// get the list from memory that matches the given type
return (List<TModel>)Memory[modelType];
}
public void LoadAllModelListsAsProperties()
{
var filter = (c) => true; // just simplified as example
foreach (KeyValuePair<Type, Type> item in AllModelTypes)
{
Type modelType = item.Key;
Type linqType = item.Value;
List<ModelBase> modelList = GetModelList<modelType>(modelType);
// !!!! ^^^^^^ error that it can't implicitly convert
// List<modelType> to List<Modelbase>
modelList.Clear();
modelList.AddRange(LoadListOfModels<linqType, modelType>(filter,
modelType.ModelFactoryFromLinq);
// !!!! ^^^^^^ error that "linqType"/"modelType" are variables,
// but get used like types; and that
// Type has no definition for "ModelFactoryFromLinq
}
// normally I would have to call something like that for every list type:
//AreaModels = LoadListOfModels<Areas, AreaModel>(
// filter, AreaModel.ModelFactoryFromLinq);
//GroupModels = LoadListOfModels<Groups, GroupModel>(
// filter, GroupModel.ModelFactoryFromLinq);
//TownModels = LoadListOfModels<Towns, TownModel>(
// filter, TownModel.ModelFactoryFromLinq);
}
public List<TModel> LoadListOfModels<TLinq, TModel>(
Func<TLinq, bool> filter,
Func<TLinq, TModel> modelFactory
)
where TLinq : class, ILinqClass
where TModel : ModelBase
{
using (LinqToSqlDataContext dc = new LinqToSqlDataContext())
{
return dc.GetTable<TLinq>()
.Where(filter)
.Select(modelFactory)
.ToList();
}
}
You cannot use a type variable as type
You can use the Base class as type => GetModelList<ModelBase>(modelType);
You dont need to use type for method LoadListOfModels when you pass the correct objects to the method. The compiler will determine the generic type from the parameters.
What is ModelFactoryFromLinq? If it is a property, you just have to use reflection or you could create a method to return the correct func<..> via the given type. If it is a method try this:
foreach (KeyValuePair<Type, Type> item in AllModelTypes)
{
Type modelType = item.Key;
Type linqType = item.Value;
List<ModelBase> modelList = GetModelList<ModelBase>(modelType);
// !!!! ^^^^^^ error that it can't implicitly convert
// List<modelType> to List<Modelbase>
modelList.Clear();
var ModelFactoryFromLinq = modelType.GetMethod("ModelFactoryFromLinq");
modelList.AddRange(LoadListOfModels(filter, modelFactoryFromLinq));
// !!!! ^^^^^^ error that "linqType"/"modelType" are variables,
// but get used like types; and that
// Type has no definition for "ModelFactoryFromLinq
}
I have to create a list, but i only know the class name
public void getList(string className)
{
IList lsPersons = (IList)Activator.CreateInstance(
typeof(List<>).MakeGenericType(Type.GetType(className))));
}
I tried so many methods but nothing works for me.
You can make a generic list but it is not useful. If you want to have a generic List<T> , somewhere you SHOULD incorporate your prior knowledge about the demanded type. For example you can do something like this:
if(className == "Employee") // this is where your prior knowledge is playing role
{
IList<Employee> lsPersons = (IList<Employee>)Activator.CreateInstance(
typeof(List<Employee>).MakeGenericType(Type.GetType(className))));
}
Also, you can make a generic list of any type via something like this:
public static class GenericListBuilder
{
public static object Build(Type type)
{
var obj = typeof(GenericListBuilder)
.GetMethod("MakeGenList", BindingFlags.Static|BindingFlags.NonPublic)
.MakeGenericMethod(new Type[] { type })
.Invoke(null, (new object[] {}));
return obj;
}
private static List<T> MakeGenList<T>()
{
return new List<T>();
}
}
And it is possible to consume it like:
var List<Employee> = GenericListBuilder.Build(typeof(Employee)) as List<Employee>;
or
IList list = GenericListBuilder.Build(Type.GetType(className)) as IList;
The last line is completely blind and I think it is very close to what you have in mind. But does it have any benefit? I don't think.
I'm trying to get an instance value of a generic type from a static field inside a generic class, and it throws this exception:
Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true
public class ManagerTemplate<Q, T> : IObjectManager
where T : Filmation.Runtime.Engine.ObjectId, new( )
where Q : ManagerTemplate<Q, T>, new( ) {
public readonly static Q Instance = new Q( ); <---- STATIC FIELD
}
private static void FindManagers( ) {
var IObjectManagerType = typeof( IObjectManager );
var managers = IObjectManagerType.Assembly
.GetTypes( )
.Where( t => !t.IsAbstract && t.GetInterfaces().Any( i => i == IObjectManagerType) );
foreach (var manager in managers) {
var fi = manager.GetField( "Instance" );
var instance = fi.GetValue( null ); <--- EXCEPTION
}
}
I have tried to use GetGenericTypeDefinition, but continues throwing the exception.
I have searched in google, but I have not found how it can be done...
Anyone knows how it can be done?
EDIT: The same using a static property
This is the workaround that I have implemented, (though I'd like to know if it can be done using reflection):
public static Q Instance { get; private set; }
static ManagerTemplate( ) {
Instance = new Q( );
Environment.Managers.Add( Instance );
}
You cannot get the value of public readonly static Q Instance = new Q( ); from the Generic Type Definition ManagerTemplate<Q, T>, simply because there is no concrete type for Q.
How do you get an instance of the generic type definition Q if you do not yet know what the concrete type for Q is? Simple: You can't.
Now... if what you want to get is an instance of a type that descends from ManagerTemplate<Q, T> where the generic type parameter Q is defined, then you actually want to exclude Generic Type Parameters from your search.
private static IEnumerable<IObjectManager> FindManagers()
{
Type type = typeof(IObjectManager);
IEnumerable<Type> managers = type.Assembly
.GetTypes()
.Where(t => !t.IsAbstract && t.GetInterfaces().Contains(type));
foreach (Type manager in managers)
{
var fi = manager.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (fi != null &&
!fi.FieldType.IsGenericParameter &&
type.IsAssignableFrom(fi.FieldType))
{
yield return (IObjectManager) fi.GetValue(null);
}
}
}
This will get you all the "Managers" defined in all classes that descend from ManagerTemplate<Q, T> that have defined what the type Q is.
The problem is that you are trying to get the Instance field from an unbound generic type, that is, a generic type with unspecified type arguments. An unbound type cannot be instantiated or its methods be called. You need a bound generic type with all the type parameters specified, but consider that each different concrete bound type won't share static fields. For example ManagerTemplate<Class1, Class2> will return a different Instance that ManagerTemplate<Class1, Class3>. All instances of ManagerTemplate<Class1, Class2> will share static fields, though.
You can use reflection to bind type arguments to unspecified type parameters of unbound generic types with Type.MakeGenericType. You will need to provide type arguments to your FindManagers class:
private static void FindManagers<Q,T>( ) {
var IObjectManagerType = typeof( IObjectManager );
var managers = IObjectManagerType.Assembly
.GetTypes( )
.Where( t => !t.IsAbstract && t.GetInterfaces().Any( i => i == IObjectManagerType) );
foreach (var manager in managers) {
var concreteType = manager.MakeGenericType(typeof(Q), typeof(T));
var fi = concreteType.GetField( "Instance" );
var instance = fi.GetValue( null );
}
}
I would solve the problem like the following way. Let's start from the code, and I will then explain why the code:
public interface IObjectManager {
}
public abstract class ObjectManager: IObjectManager {
static IEnumerable<IObjectManager> ManagerInstancesIterator() {
foreach(var managerType in managerTypes) {
var info=managerType.BaseType.GetField("Instance");
var instance=info.GetValue(null) as IObjectManager;
yield return instance;
}
}
public static IObjectManager[] FindManagerInstances() {
return ManagerInstancesIterator().ToArray();
}
public ObjectManager() {
managerTypes.Add(this.GetType());
}
static readonly HashSet<Type> managerTypes=new HashSet<Type>();
}
public class ManagerTemplate<Q, T>: ObjectManager
where T: new()
where Q: ManagerTemplate<Q, T>, new() {
public readonly static Q Instance=new Q();
}
public class CuriousClass<T>
: ManagerTemplate<CuriousClass<T>, T> where T: new() {
}
the test code:
public static partial class TestClass {
public static void TestMethod() {
var instanceByObject=CuriousClass<object>.Instance;
var instanceByInt32=CuriousClass<int>.Instance;
var instances=ObjectManager.FindManagerInstances();
}
}
Note the constraint of Filmation.Runtime.Engine.ObjectId is removed temporarily, you can add it back as your requirement.
You are using the curiously recurring template pattern, and the consumers' code in fact does not have a way to instantiate ManagerTemplate<Q, T> without implementing a concrete class which inherits from ManagerTemplate<Q, T>.
For the reason you encountered the exception, is pointed out by other answers. The class ManagerTemplate<Q, T> is an open generic class, that is, unless you specified the type arguments, the generic class is just a definition; it doesn't have a type instance.
The close generic type would be in the runtime types cache, and doesn't exist in the assembly. So the easiest way to get the type instances which are really used to instantiate objects, is store them in a collection. But as ManagerTemplate<Q, T> is generic, if we declare a collection in its class declaration, then there would be different collections for each close generic type. This is the reason why a base class ObjectManager. But, we don't want itself be instantiated, so it's abstract.
For why managerTypes.Add(this.GetType()); is in an instance constructor instead of a class initializer, the reason is simple, a static constructor doesn't let us know what type is this.
So in conclution, I consider a design in this way as a practical solution.
i'm trying to make a mixed collection of Types. I know the types at the start.. but I can't seem to figure out the syntax to make the collection, etc.
eg.
....
// I leave the typo there, for embarrassment :(
Initialize(new []{ typeof(Cat), typeof(Dog), typeof(JohnSkeet) });
...
public Foo Initialize(IEnumerable<Type> types)
{
// for each type, set up the inmemory storage.
foreach(var type in types)
{
// ????
// Create an empty list, which will only contain this 'type'
// I'm guessing, an IDictionary<type, ICollection<type>>().. thingy ?
}
}
public ICollection<Type> SomeTypeData(Type type)
{
// Return the collection, for this type.
}
Does this mane sense? Is this possible?
Okay, now that I think I know what you want, it would look something like this:
// This can't really be *properly* statically typed
private readonly Dictionary<Type, object> typeMap = new
Dictionary<Type, object>();
public Foo Initialize(IEnumerable<Type> types)
{
Type genericListType = typeof(List<>);
foreach(var type in types)
{
// MakeGenericType is really badly named
Type constructedListType = genericListType.MakeGenericType(type);
typeMap[type] = Activator.CreateInstance(constructedListType);
}
}
// We can't express this particularly safely either,
// although we *could* return the non-generic IList
public object SomeTypeData(Type type)
{
return typeMap[type];
}
// This *is* statically typed, although we need to cast inside
public IList<T> SomeTypeData<T>()
{
return (IList<T>) typeMap[typeof(T)];
}
See this blog post for a similar example.
Note that basically you're trying to represent something which generics simply can't handle, in terms of the internal dictionary type... and the first form of SomeTypeData can't be statically typed either... because that means knowing the type at compile time when we'll only actually be given it at execution time.
It looks to me like you're trying to create some kind of instance repository; a class that stores a list of instances of a given type.
Here's an example implementation. I've included both a generic and non-generic version of the SomeTypeData method:
public class InstanceRepository
{
private IDictionary<Type, ICollection> _Instances = new Dictionary<Type, ICollection>();
public ICollection SomeTypeData(Type type)
{
ICollection instanceList;
if (!_Instances.TryGetValue(type, out instanceList))
{
// this type does not exist in our dictionary, so let's create a new empty list
// we could do this:
//instanceList = new List<object>();
// but let's use reflection to make a more type-specific List<T> instance:
instanceList = (ICollection)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
// now add it to the dictionary
_Instances.Add(type, instanceList);
}
// Return the collection, for this type.
return instanceList;
}
public IList<T> SomeTypeData<T>()
{
Type type = typeof(T);
ICollection instanceList;
if (!_Instances.TryGetValue(typeof(T), out instanceList))
{
instanceList = new List<T>();
_Instances.Add(type, instanceList);
}
// here we are assuming that all of the lists in our dictionary implement IList<T>.
// This is a pretty safe assumption, since the dictionary is private and we know that
// this class always creates List<T> objects to put into the dictionary.
return (IList<T>)instanceList;
}
}
Below is a usage example:
Generic:
InstanceRepository repository = new InstanceRepository();
var listOfCats = repository.SomeTypeData<Cat>();
listOfCats.Add(new Cat());
Cat firstCat = listOfCats[0];
Console.WriteLine(listOfCats.GetType().FullName);
Non-Generic:
InstanceRepository repository = new InstanceRepository();
var listOfCats = (IList<Cat>)repository.SomeTypeData(typeof(Cat));
listOfCats.Add(new Cat());
Cat firstCat = listOfCats[0];
Console.WriteLine(listOfCats.GetType().FullName);
I guess you want something like
_dict[typeof(Cat)]=new List<Cat>();
_dict[typeof(Dog)]=new List<Dog>();
only programatically based on given types?
Something like this should work:
public void Initialize(IEnumerable<Type> types)
{
foreach(var type in types)
{
var list = Activator.CreateInstance(Type.GetType("System.Collections.Generic.List`1").MakeGenericType(type));
_cache[type] = list;
}
}
public ICollection<T> Get<T>()
{
object list;
if (_cache.TryGetValue(typeof(T), out list)
{
return list as ICollection<T>;
}
else
{
...
}
}
var cats = Get<Cat>();
I'm not sure I fully understand you're question, but if you already have an IEnumerable<Type> which contains an enumeration of Type objects, then why not just use that to initialize some type of Collection (such as List<Type>)?
public ICollection<Type> Initialize(IEnumerable<Type> types)
{
ICollection<Type> collection = new List<Type>(types);
return collection;
}