I want to be able use a type instead of T with Dbset
For example Dbset(myType) instead of DbSet
EF core 3.1 requires you to use DbSet and all the functionality is encapsulated in Extension methods.
Here's a solution I went with for EF 3.1
Hoping that since I've already been through the pain, I can share my solution for the next poor soul who has to do this.
Usage
var dbSet = new DbSetFacade(myType);
dynamic results = await dbSet.ToListAsync();
I'll just share ToList,Count for now to keep it brief!
public class DbSetFacade
{
private static ReadOnlyDictionary<EfQueryExtensionType, MethodInfo> _extensionMethodInfos = GetExtensionMethodInfos();
private readonly object _dbSet;
private readonly ReadOnlyDictionary<EfQueryExtensionType, MethodInfo> _genericInstanceMethodInfos;
public DbSetFacade(DbContext context, Type entityType)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (entityType == null) throw new ArgumentNullException(nameof(entityType));
_dbSet = GetDbSetInstance(context, entityType);
_genericInstanceMethodInfos = new ReadOnlyDictionary<EfQueryExtensionType, MethodInfo>(new Dictionary<EfQueryExtensionType, MethodInfo>
{
[EfQueryExtensionType.AsNoTracking] = _extensionMethodInfos[EfQueryExtensionType.AsNoTracking].MakeGenericMethod(entityType),
[EfQueryExtensionType.ToListAsync] = _extensionMethodInfos[EfQueryExtensionType.ToListAsync].MakeGenericMethod(entityType),
[EfQueryExtensionType.CountAsyncWithoutPredicate] = _extensionMethodInfos[EfQueryExtensionType.CountAsyncWithoutPredicate].MakeGenericMethod(entityType),
});
}
enum EfQueryExtensionType
{
AsNoTracking,
ToListAsync,
//FirstOrDefaultAsyncWithPredicate,
//FirstOrDefaultAsyncWithoutPredicate,
//ContainsAsync,
//CountAsyncWithPredicate,
CountAsyncWithoutPredicate
}
public Task<int> CountAsync(CancellationToken token = default) => _genericInstanceMethodInfos[EfQueryExtensionType.CountAsyncWithoutPredicate].Invoke(null, new object[] { _dbSet, token }) as Task<int>;
public async Task<dynamic> ToListAsync(CancellationToken token = default)
{
var noTrackingSet = _genericInstanceMethodInfos[EfQueryExtensionType.AsNoTracking].Invoke(null, new object[] { _dbSet });
var toList = _genericInstanceMethodInfos[EfQueryExtensionType.ToListAsync].Invoke(null, new object[] { noTrackingSet, token }) as Task;
await toList.ConfigureAwait(false);
return (object)((dynamic)toList).Result;
}
private static object GetDbSetInstance(DbContext context, Type type)
{
return typeof(DbContext).GetMethods()
.FirstOrDefault(p => p.Name == "Set" && p.ContainsGenericParameters)
.MakeGenericMethod(type)
.Invoke(context, null);
}
static ReadOnlyDictionary<EfQueryExtensionType, MethodInfo> GetExtensionMethodInfos()
{
var extensionMethods = typeof(EntityFrameworkQueryableExtensions)
.GetTypeInfo()
.GetMethods()
.Where(x => x.ContainsGenericParameters && x.GetParameters()[0].ParameterType.IsGenericType);
return new ReadOnlyDictionary<EfQueryExtensionType, MethodInfo>(new Dictionary<EfQueryExtensionType, MethodInfo>
{
[EfQueryExtensionType.AsNoTracking] = extensionMethods.FirstOrDefault(x => x.Name == "AsNoTracking"),
[EfQueryExtensionType.ToListAsync] = extensionMethods.FirstOrDefault(x => x.Name == "ToListAsync" && x.GetParameters().Count() == 2),
[EfQueryExtensionType.CountAsyncWithoutPredicate] = extensionMethods.FirstOrDefault(x => x.Name == "CountAsync" && x.GetParameters().Count() == 2)
});
}
}
I need to call a local generic method by reflection, but I can't figure out how to do it.
My core problem is that I'm using reflection to populate an POCO object from a set of queries reading EAV data from a MySQL database. I'm happy to show this code, but it's lengthy and complex. I've created a simplified example that I think summarizes the problem nicely.
Consider this code:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
Console.WriteLine(Compute<Foo>(new[] { "A", "B" }));
}
public class Foo { }
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], int> value)
{
_store[typeof(T)] = value;
}
public Func<string[], int> Fetch<T>()
{
return (Func<string[], int>)_store[typeof(T)];
}
}
I have a Repository type that can store a Func<string[], int> indexed by Type.
In my Main method I have instantiated an instance of Repository and stored xs => xs.Count() against the type Foo.
I have a local generic method Compute that I can call easily when I know the type of M at compile time. The call Compute<Foo>(new[] { "A", "B" }) computes 2. That's no problem.
The issue occurs when I only know M at run-time.
Let's say I have this type:
public class Bar
{
public Foo Value;
}
Now my code in Main looks like this:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
var runtimeType = typeof(Bar).GetField("Value").FieldType;
var reflectedMethod = /* What Goes Here ??? */
Console.WriteLine(reflectedMethod.Invoke(new[] { "A", "B" }));
}
I am thoroughly stumped with the /* What Goes Here ??? */ part.
I found a similar question but that method doesn't deal with a local generic method.
Can someone help with how to invoke such a method at run-time?
Here's the full working code. It runs in LINQPad with NuGet "System.Interactive" and a also Maybe monad library, with a reference to my MySQL database.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class MagentoAttribute : Attribute
{
public string AttributeCode { get; private set; }
public MagentoAttribute(string code)
{
this.AttributeCode = code;
}
}
public static class Magento
{
public abstract class ProductBase
{
public uint EntityId { get; private set; }
public string Sku { get; private set; }
public decimal? Quantity { get; private set; }
}
public static T[] GetProducts<T>(this UserQuery #this) where T : ProductBase, new()
{
var setEntityId = typeof(ProductBase).GetProperty("EntityId", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setSku = typeof(ProductBase).GetProperty("Sku", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setQuantity = typeof(ProductBase).GetProperty("Quantity", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var results =
(
from cpe in #this.catalog_product_entity
where cpe.sku != null && !cpe.sku.StartsWith("SHIP")
join csi in #this.cataloginventory_stock_item on cpe.entity_id equals csi.product_id
select new { cpe.entity_id, cpe.sku, csi.qty }
)
.ToArray()
.Select(x =>
{
var t = new T();
setEntityId.SetValue(t, x.entity_id);
setSku.SetValue(t, x.sku);
setQuantity.SetValue(t, x.qty);
return t;
})
.ToArray();
Func<string[], Dictionary<string, Func<uint, Maybe<string>>>> getStrings = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_varchar on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.Concat(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_text on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<string>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<int?>>>> getIntegers = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_int on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<int?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<DateTime?>>>> getDateTimes = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_datetime on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<DateTime?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<decimal?>>>> getDecimals = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_decimal on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<decimal?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
var prerepo = new Prerepository();
prerepo.Store<Maybe<string>>(getStrings);
prerepo.Store<Maybe<int?>>(getIntegers);
prerepo.Store<Maybe<DateTime?>>(getDateTimes);
prerepo.Store<Maybe<decimal?>>(getDecimals);
var collapse = new Dictionary<Type, Delegate>()
{
{ typeof(Maybe<int?>), (Func<Maybe<int?>, Maybe<int?>>)(m => m) },
{ typeof(Maybe<int>), (Func<Maybe<int?>, Maybe<int>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<int>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(int?), (Func<Maybe<int?>, int?>)(m => m.HasValue ? m.Value : (int?)null) },
{ typeof(int), (Func<Maybe<int?>, int>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(int)) },
{ typeof(Maybe<decimal?>), (Func<Maybe<decimal?>, Maybe<decimal?>>)(m => m) },
{ typeof(Maybe<decimal>), (Func<Maybe<decimal?>, Maybe<decimal>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<decimal>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(decimal?), (Func<Maybe<decimal?>, decimal?>)(m => m.HasValue ? m.Value : (decimal?)null) },
{ typeof(decimal), (Func<Maybe<decimal?>, decimal>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(decimal)) },
{ typeof(Maybe<DateTime?>), (Func<Maybe<DateTime?>, Maybe<DateTime?>>)(m => m) },
{ typeof(Maybe<DateTime>), (Func<Maybe<DateTime?>, Maybe<DateTime>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<DateTime>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(DateTime?), (Func<Maybe<DateTime?>, DateTime?>)(m => m.HasValue ? m.Value : (DateTime?)null) },
{ typeof(Maybe<string>), (Func<Maybe<string>, Maybe<string>>)(m => m) },
{ typeof(string), (Func<Maybe<string>, string>)(m => m.HasValue ? m.Value : default(string)) },
};
var attributes =
Enumerable
.Concat(
typeof(T)
.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Field = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Field.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Field.FieldType
}),
typeof(T)
.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Property = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Property.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Property.PropertyType
}))
.Where(x => collapse.ContainsKey(x.AttributeType))
.ToArray();
var postrepo = new Postrepository();
postrepo.Store<Maybe<int?>>(null);
void Fetch<M>(string[] attributeCodes) => postrepo.Store<M>(prerepo.Fetch<M>()(attributeCodes));
void InvokeHelper(Action<string[]> prototype, Type type, object data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
concreteMethod.Invoke(prototype.Target, new[] { data });
}
Type lift(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Maybe<>))
{
t = t.GetGenericArguments()[0];
}
if (!t.IsGenericType)
{
if (t.IsValueType)
{
t = typeof(Nullable<>).MakeGenericType(t);
}
}
t = typeof(Maybe<>).MakeGenericType(t);
return t;
}
(
from a in attributes
let m = lift(a.AttributeType)
group a.AttributeCode by m
)
.ForEach(x => InvokeHelper(Fetch<object>, x.Key, x.Distinct().ToArray()));
void Inject<M, P>(string attributeCode, Action<object, object> inject)
{
var b = postrepo.Fetch<M>();
var d = b[attributeCode];
var fmp = (Func<M, P>)collapse[typeof(P)];
results
.ForEach(r =>
{
var m = d(r.EntityId);
inject(r, fmp(m));
});
}
void InvokeHelper2(Action<string, Action<object, object>> prototype, Type[] types, object[] data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(types);
concreteMethod.Invoke(prototype.Target, data);
}
(
from a in attributes
let m = lift(a.AttributeType)
group new { a.AttributeType, a.Inject } by new { a.AttributeCode, m }
)
.ForEach(xs =>
{
xs
.ForEach(y =>
{
InvokeHelper2(Inject<object, object>, new[] { xs.Key.m, y.AttributeType }, new object[] { xs.Key.AttributeCode, y.Inject });
});
});
return results;
}
public class Prerepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], Dictionary<string, Func<uint, T>>> value)
{
_store[typeof(T)] = value;
}
public Func<string[], Dictionary<string, Func<uint, T>>> Fetch<T>()
{
return (Func<string[], Dictionary<string, Func<uint, T>>>)_store[typeof(T)];
}
}
public class Postrepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Dictionary<string, Func<uint, T>> value)
{
_store[typeof(T)] = value;
}
public Dictionary<string, Func<uint, T>> Fetch<T>()
{
return (Dictionary<string, Func<uint, T>>)_store[typeof(T)];
}
}
}
Now I can write this code:
public class Product : Magento.ProductBase
{
[Magento("name")] public string Name { get; private set; }
[Magento("is_deleted")] private Maybe<int?> _is_deleted = Maybe<int?>.Nothing;
public bool IsDeleted { get => _is_deleted.HasValue ? _is_deleted.Value == 1 : false; }
[Magento("brand")] private Maybe<string> _brand { get; set; } = Maybe<string>.Nothing;
public string Brand { get => _brand.HasValue ? _brand.Value : "(missing)"; }
[Magento("cost")] public decimal Cost { get; private set; }
}
And then this:
var ps =
Magento
.GetProducts<Product>(this)
.Where(x => x.Cost == 0m)
.Where(x => !x.IsDeleted)
.Where(x => x.Quantity > 0m);
And I now have a strongly-typed way to read the Magento EAV data structure.
Jon Skeets answer will work for your problem too.
I only changed the signature to match your requirements:
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
var computeResult = InvokeHelper(Compute<object>, new[] { "A", "B" }, runtimeKnownTime);
Console.WriteLine(computeResult);
This uses the following implementation of InvokeHelper:
// This code was created by Jon Skeet : https://stackoverflow.com/a/43349127/2729609
// I only changed Action<int> to Func<int, int> and changed the return type.
private static int InvokeHelper(Func<string[], int> int32Func, object data, Type type)
{
// You probably want to validate that it really is a generic method...
var method = int32Func.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
return (int)concreteMethod.Invoke(int32Func.Target, new[] { data });
}
DEMO
BUT I don't see the need to use this hack. You don't need the generic type in your case.
Change the repository to:
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store(Type id, Func<string[], int> value)
{
_store[id] = value;
}
// optional
public void Store<T>(Func<string[], int> value) => this.Store(typeof(T), value);
public Func<string[], int> Fetch(Type id)
{
return (Func<string[], int>)_store[id];
}
// optional
public Func<string[], int> Fetch<T>() => this.Fetch(typeof(T));
}
And you can use it without generics:
var repository = new Repository();
repository.Store(typeof(Foo), xs => xs.Count());
int Compute(Type id, string[] source) => repository.Fetch(id).Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
Console.WriteLine(Compute(runtimeKnownTime, new[] { "A", "B" }));
If you want, create generic overloads for the methods Fetch and Store that call the shown implementation using typeof(T). I markted this method with optional in my sample implemenation.
I have this type that contains two overloads of a generic method. I like to retrieve one of the overloads (with the Func<T> parameter) using reflection. The problem however is that I can't find the correct parameter type to supply the Type.GetMethod(string, Type[]) method with.
Here is my class definition:
public class Foo
{
public void Bar<T>(Func<T> f) { }
public void Bar<T>(Action<T> a) { }
}
And this is what I've come up with, unfortunately without succes:
[TestMethod]
public void Test1()
{
Type parameterType = typeof(Func<>);
var method = typeof(Foo).GetMethod("Bar", new Type[] { parameterType });
Assert.IsNotNull(method); // Fails
}
How can I get the MethodInfo of a generic method of which I know the parameters?
Why don't you use expression trees? This makes it much easier:
public static MethodInfo GetMethod<T>(
Expression<Action<T>> methodSelector)
{
var body = (MethodCallExpression)methodSelector.Body;
return body.Method;
}
[TestMethod]
public void Test1()
{
var expectedMethod = typeof(Foo)
.GetMethod("Bar", new Type[] { typeof(Func<>) });
var actualMethod =
GetMethod<Foo>(foo => foo.Bar<object>((Func<object>)null)
.GetGenericMethodDefinition();
Assert.AreEqual(expectedMethod, actualMethod);
}
Surprisingly, it looks like you'll need to call GetMethods() and loop over the methods until you find the one you want.
For example:
var yourMethod = typeof(Foo).GetMethods()
.First(m => m.Name == "Bar"
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType.ContainsGenericParameters
&& m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Func<>));
I don't think you can do this directly using GetMethod. I suspect you'll have to iterate over all the methods called Bar, then:
Check that the method has one type parameter
Check that the method has one normal parameter
Use the type parameter to make a Func<T> (with typeof(Func<>).MakeGenericType) and check that the parameter type matches that.
LINQ is good for this sort of thing. Complete sample:
using System;
using System.Reflection;
using System.Linq;
public class Foo
{
public void Bar<T>(Func<T> f) { }
public void Bar<T>(Action<T> a) { }
}
class Test
{
static void Main()
{
var methods = from method in typeof(Foo).GetMethods()
where method.Name == "Bar"
let typeArgs = method.GetGenericArguments()
where typeArgs.Length == 1
let parameters = method.GetParameters()
where parameters.Length == 1
where parameters[0].ParameterType ==
typeof(Func<>).MakeGenericType(typeArgs[0])
select method;
Console.WriteLine("Matching methods...");
foreach (var method in methods)
{
Console.WriteLine(method);
}
}
}
Basically generics and reflection are really nasty in combination, I'm afraid :(
You need to specify a concrete type using MethodInfo.MakeGenericMethod.
However, I should point out, that getting the right type to invoke MakeGenericMethod on is not easy when you have an overloaded generic method.
Here is an example:
var method = typeof(Foo)
.GetMethods()
.Where(x => x.Name == "Bar")
.Where(x => x.IsGenericMethod)
.Where(x => x.GetGenericArguments().Length == 1)
.Where(x => x.GetParameters().Length == 1)
.Where(x =>
x.GetParameters()[0].ParameterType ==
typeof(Action<>).MakeGenericType(x.GetGenericArguments()[0])
)
.Single();
method = method.MakeGenericMethod(new Type[] { typeof(int) });
Foo foo = new Foo();
method.Invoke(foo, new Func<int>[] { () => return 42; });
You'll struggle with just with just GetMethod - you could try something along the lines of;
var method = (from m in typeof(Foo).GetMethods()
where
m.IsGenericMethodDefinition == true &&
m.Name == "Bar" &&
m.GetParameters().Length > 0 &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == parameterType
select m).FirstOrDefault();
I would like to find all classes in my program that implements a given class with a specific call as the generic types.
In the example below would i like to find all classes that implements MyBaseClass<MyScraper, MyElance> in this case would it be MyProperty and not OtherProperty as it implements other generic classes.
How can this be done?
public class MyProperty : MyBaseClass<MyScraper, MyElance>
{
public override string test()
{
var test = base.test();
test += " + the new";
return test;
}
}
public class OtherProperty : MyBaseClass<OtherScraper, OtherElance>
{
public override string test()
{
var test = base.test();
test += " + the other";
return test;
}
}
public class MyBaseClass<S, E>
where S : IScraper
where E : IElance
{
public virtual string test()
{
return "base";
}
}
Edit:
Found a solution, but please tell me if there are a better way
var test = from x in Assembly.GetAssembly(typeof(Program)).GetTypes()
let y = x.BaseType
where !x.IsAbstract && !x.IsInterface &&
y != null && y.IsGenericType &&
y.GetGenericTypeDefinition() == typeof(MyBaseClass<,>) &&
y.GenericTypeArguments[0] == typeof(MyScraper) &&
y.GenericTypeArguments[1] == typeof(MyElance)
select x;
Final solution:
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => !x.IsAbstract && !x.IsInterface)
.Where(x => x.BaseType != null &&
x.BaseType.IsGenericType &&
x.BaseType.GetGenericTypeDefinition() == typeof (MyBaseClass<,>) &&
x.BaseType.GenericTypeArguments[0] == typeof (MyScraper) &&
x.BaseType.GenericTypeArguments[1] == typeof (MyElance))
.Select(x => x).ToList();
Assembly scanning is pretty much the best way.
In addition to the algorythm you have, I recommend scanning the AppDomain instead of the specific assembly - this will allow you (or others!) toextend your library more readily...
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
//...
(Disclaimer Pardon the use of Linq calls here, but I detest syntactical linq)
In addition, make sure any other assemblies are loaded, to ensure you don't miss something at scan-time.
var searchType = typeof(MyBaseClass<MyScraper, MyElance>);
var types =
AppDomain.CurrentDomain.GetAssemblies()
// For search only on assemblies where could exsist this types
.Where(a => a.GetName().Name == searchType.Assembly.GetName().Name || a.GetReferencedAssemblies().Any(n => n.Name == searchType.Assembly.GetName().Name))
.Where(t => !t.IsAbstract && !t.IsInterface)
.Select(t => t.GetTypes().Where(a => searchType.IsAssignableFrom(a)))
.SelectMany(a => a);
I use this version, to search in all loaded assemblies, with additional filter not to process assemblies that could not contain my type
Try this:
//var desiredImplementation = typeof (MyBaseClass<>).MakeGenericType(typeof (MyScraper), typeof (MyElance));
var desiredImplementation = typeof (MyBaseClass<MyScraper, MyElance>);
var implementingTypes = Assembly
.GetExecutingAssembly()
.GetExportedTypes()
.Where(type => desiredImplementation.IsAssignableFrom(type))
.ToList();
You can replace GetExportedTypes() with GetTypes() and BindingFlags to further introspect the assembly too.
I have a small dependency injection framework, and I am trying to make it resolve Lazy<> instances dynamically. The idea is to do something like that:
DIContainer.Register<IDbCommand,SqlCommand>();
var lazyCommand = DIContainer.Resolve<Lazy<IDbCommand>>();
I read the other day that Autofac was able of doing that.
I am stuck trying to set the constructor for that Lazy<> instance. In the next test code, a exception is thrown because the desired type constructor is expecting a Func<arg>, but I am passing a Func<Object>:
static readonly Type _lazyType = typeof(Lazy<>);
static Object ResolveTest(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == _lazyType)
{
var arg = type.GetGenericArguments()[0];
return Activator.CreateInstance(_lazyType.MakeGenericType(arg), new Func<Object>(() => ResolveType(arg)));
}
else
return ResolveType(type);
}
I am out of ideas about how to create a delegate that fits for the Lazy<> constructor parameter. Any idea?
Cheers.
That's not trivial. One possible solution would be to work with reflection:
Create a generic ResolveType method:
public static T ResolveType<T>()
{
return (T)ResolveType(typeof(T));
}
Create a delegate that uses this method:
// You probably want to cache this MethodInfo:
var method = typeof(TypeContainingResolveType)
.GetMethods()
.Single(x => x.IsGenericMethod &&
x.Name == "ResolveType")
.MakeGenericMethod(arg);
var delegate = Delegate.CreateDelegate(
typeof(Func<>).MakeGenericType(arg),
method);
Use that delegate:
return Activator.CreateInstance(_lazyType.MakeGenericType(arg), delegate);
This app outputs "True" and "0". I.e. ResolveTest(typeof(Lazy<int>)) returns a Lazy<int> object, constructed like you wanted.
using System;
using System.Linq.Expressions;
namespace TestApp
{
public class Class1
{
public static void Main()
{
object lazyInt = ResolveTest(typeof(Lazy<int>));
Console.WriteLine(lazyInt.GetType() == typeof(Lazy<int>));
Console.WriteLine(((Lazy<int>)lazyInt).Value);
}
static readonly Type _lazyType = typeof(Lazy<>);
static Object ResolveTest(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == _lazyType)
{
var arg = type.GetGenericArguments()[0];
var lazyArgType = _lazyType.MakeGenericType(arg);
var funcArgType = typeof(Func<>).MakeGenericType(arg);
var funcCtor = lazyArgType.GetConstructor(new[] { funcArgType });
Expression<Func<object>> f = () => ResolveTest(arg);
var func = typeof(Class1).GetMethod("BuildCastedThing").MakeGenericMethod(arg).Invoke(null, new[] { f });
var arguments = new object[] { func };
var retVal = funcCtor.Invoke(arguments);
return retVal;
}
else
return ResolveType(type);
}
public static object ResolveType(Type type)
{
return Activator.CreateInstance(type);
}
public static Func<T> BuildCastedThing<T>(Expression<Func<object>> f)
{
Expression<Func<T>> expr =
Expression.Lambda<Func<T>>(
Expression.Convert(
Expression.Invoke(f),
typeof(T)));
return expr.Compile();
}
}
}
This is a way to rewrite ResolveTest as a generic Resolve<T> (e.g. Resolve<int> returns Lazy<int>). This is a little different, since there's no equivalent to ResolveTest(typeof(int)), which returns an int.
static Lazy<T> Resolve<T>()
{
var arg = typeof(T);
return new Lazy<T>(() => (T)ResolveType(arg));
}
Or with a generic ResolveType<T>:
static Lazy<T> Resolve<T>()
{
return new Lazy<T>(() => ResolveType<T>());
}
public static T ResolveType<T>()
{
return Activator.CreateInstance<T>();
}
public static Object ResolveTest(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == _lazyType)
{
var arg = type.GetGenericArguments()[0];
Expression<Func<object>> expressionWithFuncOfTypeObject = () => ResolveType(arg);
UnaryExpression expressionThatEvaluatesToAnObjectOfTypeArg = Expression.Convert(expressionWithFuncOfTypeObject.Body, arg);
LambdaExpression expressionWithFuncOfTypeArg = Expression.Lambda(typeof(Func<>).MakeGenericType(arg), expressionThatEvaluatesToAnObjectOfTypeArg);
Delegate funcOfTypeArg = expressionWithFuncOfTypeArg.Compile(); // <-- At runtime this will be of type Func<T>
return Activator.CreateInstance(_lazyType.MakeGenericType(arg), funcOfTypeArg);
}
else
return ResolveType(type);
}