How to get the concrete implementation of an interface by the generic type?
I need some help figure out how to use reflection to get the concrete implementation based of the Dto type:
public interface IDocumentService<TDto>
{
}
public interface ICommentService: IDoumentService<CommentDto>
{
}
public abstract class DocumentService<TEntity,TDto>: IDocumentService<TDto> where TEntity: Entity, where TDto: Dto
{
}
public class CommentService: DocumentService<开发者_运维问答;Comment,CommentDto>, ICommentService
{
}
So, what I want to do, is pass the CommentDto to a method so I can get to the CommentService.
public IDocumentService<TDto> GetDocumentService<TDto>()
{
//based on the TDto type I want to find the concrete that
//implements IDocumentService<TDto>
}
I would call it like this:
var commentDocumentService = GetDocumentService<CommentDto>();
So, I would get back CommentService, knowing that I would only see the methods part of the IDocumentService interface.
Here is a possible implementation for GetDocumentService.
public static IDocumentService<TDto> GetDocumentService<TDto>()
{
// Gets the type for IDocumentService
Type tDto=typeof(IDocumentService<TDto>);
Type tConcrete=null;
foreach(Type t in Assembly.GetExecutingAssembly().GetTypes()){
// Find a type that implements tDto and is concrete.
// Assumes that the type is found in the executing assembly.
if(tDto.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface){
tConcrete=t;
break;
}
}
// Create an instance of the concrete type
object o=Activator.CreateInstance(tConcrete);
return (IDocumentService<TDto>)o;
}
It wasn't clear whether you wanted to return a new object, so I assumed so.
EDIT:
Due to your comment, here is a modified version of GetDocumentService. The disadvantage is that you need to specify another type parameter. The advantage, though, is that this approach provides a certain degree of type safety, since both type parameters must be compatible.
public static T GetDocumentService<TDto, T>() where T : IDocumentService<TDto>
{
// Gets the type for IDocumentService
Type tDto=typeof(T);
Type tConcrete=null;
foreach(Type t in Assembly.GetExecutingAssembly().GetTypes()){
// Find a type that implements tDto and is concrete.
// Assumes that the type is found in the calling assembly.
if(tDto.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface){
tConcrete=t;
break;
}
}
// Create an instance of the concrete type
object o=Activator.CreateInstance(tConcrete);
return (T)o;
}
EDIT 2:
If I understand correctly, you want to get the other interfaces implemented by the type of the return value of GetDocumentService. For example, GetDocumentService<CommentDto>
returns an object of type CommentService
that implements the ICommentService
interface. If I understand correctly, the return value should be a Type object (for example, the return value could be typeof(ICommentService)
). Once you have the type, you should call the type's FullName
property to get the type's name.
Use the following method on the return value of GetDocumentService
to get the type of interface implemented by that value, for instance, typeof(ICommentService)
.
public static Type GetDocumentServiceType<TDto>(IDocumentService<TDto> obj){
Type tDto=typeof(IDocumentService<TDto>);
foreach(Type iface in obj.GetType().GetInterfaces()){
if(tDto.IsAssignableFrom(iface) && !iface.Equals(tDto)){
return iface;
}
}
return null;
}
Firstly, your CommentService
class needs to be discoverable somehow, given the type of TDto
. You can search all the loaded types from all the assemblies in the current AppDomain
- however that will be painfully slow.
So you have the following viable options:
- Use an attribute on the assembly that defines
CommentService
. - Use configuration to define this information.
- Use MEF.
I'll demonstrate the first approach. Firstly create the attribute:
[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)]
public sealed class DtoProviderAttribute : Attribute
{
public Type ProvidedType { get; private set; }
public Type ProviderType { get; private set; }
public DtoProviderAttribute(Type providedType, Type providerType)
{
ProvidedType = providedType;
ProviderType = providerType;
}
}
And then apply it to the assembly that defines CommentService
(typically you would put in AssemblyInfo.cs
).
[assembly:DtoProvider(typeof(CommentDto), typeof(CommentService))]
Now you can use those attributes to search for the concrete implementations.
public class ServiceFactory
{
private static readonly Dictionary<RuntimeTypeHandle, Func<object>> _dtoMappings = new Dictionary<RuntimeTypeHandle, Func<object>>();
public static IDocumentService<TDto> GetDocumentService<TDto>()
{
var rth = typeof(TDto).TypeHandle;
Func<object> concreteFactory;
lock (_dtoMappings)
{
if (_dtoMappings.TryGetValue(typeof(TDto).TypeHandle, out concreteFactory))
return (IDocumentService<TDto>)concreteFactory();
FillMappings();
if (!_dtoMappings.TryGetValue(typeof(TDto).TypeHandle, out concreteFactory))
throw new Exception("No concrete implementation found.");
return (IDocumentService<TDto>)concreteFactory();
}
}
private static void FillMappings()
{
// You would only need to change this method if you used the configuration-based approach.
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var attrs = assembly.GetCustomAttributes(typeof(DtoProviderAttribute), false);
foreach (DtoProviderAttribute item in attrs)
{
if (!_dtoMappings.ContainsKey(item.ProvidedType.TypeHandle))
{
var expr = Expression.Lambda<Func<object>>(Expression.Convert(Expression.New(item.ProviderType), typeof(object)));
_dtoMappings.Add(item.ProvidedType.TypeHandle, expr.Compile());
}
}
}
}
}
As 'Rune' pointed out: because of the cache the overhead of searching all the assemblies is low:
private static void FillMappings()
{
foreach (var type in AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).Where(x => x.IsClass && !x.IsAbstract))
{
foreach (var iface in type.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDocumentService<>)))
{
var arg = iface.GetGenericArguments()[0];
if (!_dtoMappings.ContainsKey(arg.TypeHandle))
{
var expr = Expression.Lambda<Func<object>>(Expression.Convert(Expression.New(type), typeof(object)));
_dtoMappings.Add(arg.TypeHandle, expr.Compile());
}
}
}
}
another possibility:
public IDocumentService<TDto> GetDocumentService<TDto>()
{
var genericParameter = typeof(TDto);
return (from type in Assembly.GetExecutingAssembly().GetTypes() // Get Types
where type.GetConstructor(Type.EmptyTypes) != null // That is concrete
let interfaces = type.GetInterfaces()
from intf in interfaces
where intf.IsGenericType // Which implement generic interface
let genarg = intf.GetGenericArguments()[0]
where genarg == genericParameter // Where generic argument is of type genericParameter
select (IDocumentService<TDto>) // Cast to IDocumentService
Activator.CreateInstance(type)).FirstOrDefault(); // Instantiate
}
精彩评论