C# call Generic method dynamically [duplicate]
Given the following Interfaces:
interface IEntity
{
int Id{get;}
}
interface IPerson : IEntity
{
string Name{get;}
int Age{get;}
}
interface ITeacher : IPerson
{
string StaffId{get;}
}
interface IStudent : IPerson
{
string StudentId{get;}
string Courses{get;}
}
interface IRepository
{
T Get<T>(int id) where T : IEntity
}
I have the following classes in my na开发者_StackOverflow社区mespace
public class EntityBase() : IEntity
{
int Id{get;set;}
}
public class Teacher : EntityBase, ITeacher{}
public class Sudent : EntityBase, IStudent{}
Currently I am implementing this IRepository as follows:
class Repository: IRepository
{
IDataContext Context{get;set;}
T Get<T>(int id) where T : EntityBase
{
if(typeof(T) == typeof(Teacher))
return Context.Get<ITeacher>(id);
if(typeof(T) == typeof(Sudent))
return Context.Get<ISudent>(id);
throw new Exception("Unknown Interface " + typeof(T).Name);
}
}
Is there a betterway of implementing this? Given that our Context has no knowledge of our data types (Teacher, Student), just its interfaces (ITeacher, IStudent).
Can something like this work?
class Repository: IRepository
{
T Get<T>(int id) where T : EntityBase
{
var MyInterface = FindInterface<T>();
return Context.Get<MyInterface>(id);
}
}
I think this will do:
class Repository: IRepository
{
IDataContext Context{get;set;}
T Get<T>(int id) where T : EntityBase
{
string[] interfaceList = new string[]
{ "ITeacher", "IStudent"};
Type interfaceType = null;
foreach (string s in interfaceList)
{
var types = typeof(T).FindInterfaces((x, y) => x.Name == y.ToString(), s);
if (types.Length > 0)
interfaceType = types[0];
}
if (interfaceType == null)
throw new Exception("Unknown Interface " + typeof(T).Name);
MethodInfo method = typeof(Context).GetMethod("Get");
MethodInfo generic = method.MakeGenericMethod(interfaceType);
var returnValue = generic.Invoke(Context, new object[] { id });
return (T)Convert.ChangeType(returnValue, typeof(T));
}
}
EDIT: As I don't know the name of your namespace, I have used the Name property to filter the interfaces. In real world usage I will suggest that you use FullName just to be sure, like this:
...
string[] interfaceList = new string[]
{ "MyNamespace.ITeacher", "MyNamespace.IStudent"};
...
var types = typeof(T).FindInterfaces((x, y) => x.FullName == y.ToString(), s);
I think you can accomplish this through reflection by finding the Get method on Context class, and invoking it as a generic call for the caller-supplied type T. I haven't tested this, but the code should look something like this:
T Get<T>(int id) where T : EntityBase
{
Type context = Context.GetType();
MethodInfo getMethod = context.GetMethod("Get", BindingFlags.Public);
MethodInfo genericGet = getMethod.MakeGenericMethod(new [] {typeof(T)});
return (T)genericGet.Invoke(Context, new object[] { id } );
}
It looks to me like you mean it the other way around. You don't want to pass interface types to Context.Get<>
, do you?
// is this what you mean?
if (typeof(T) == typeof(ITeacher))
return Context.Get<Teacher>(id);
If it is, you'll need to use MakeGenericMethod
, see this for an example (note the caching part).
If it is not, you might be misunderstanding some concepts of LINQ and/or Repository pattern.
However I'm curious why did you decide to use interfaces. LINQ objects are POCO anyways, why adding another layer of abstraction which involves (grrsh!) calling generic methods on DataContext via reflection?
A simple return Context.Get<T>(id)
could be accomplished as following:
class Repository : IRepository
{
public IDataContext Context { get; set; }
public T Get<T>(int id) where T : IEntity, new()
{
return Context.Get<T>(id);
}
}
Following is your object/interface model with implementation for the Context
interface IEntity
{
int Id{get;}
}
interface IPerson : IEntity
{
}
interface ITeacher : IPerson
{
}
interface IStudent : IPerson
{
}
interface IDataContext
{
T Get<T>(int id) where T:new();
}
interface IRepository
{
T Get<T>(int id) where T : IEntity , new() ;
}
public class EntityBase : IEntity
{
public virtual int Id{get;set;}
}
public class Teacher : EntityBase, ITeacher {
int id=0;
public override int Id {
get { return this.id; }
set { this.id = value; }
}
}
public class Student : EntityBase, IStudent
{
int id=0;
public override int Id {
get { return this.id; }
set { this.id = value; }
}
}
class Context<T>: IDataContext where T: EntityBase, new()
{
ArrayList store;
public Context(int dataSize)
{
store = new ArrayList(dataSize);
for (int i = 0; i < dataSize; i++)
{
T t = new T();
t.Id = i;
store.Add(t);
}
}
public T Get<T>(int i) where T:new()
{
if (i<store.Count)
{
return (T)store[i];
}
else
{
return default(T);
}
}
}
Now finally the main method class to demonstrate that it all hangs together nicely.
using System;
using System.Collections;
class MyClass
{
static void Main(string[] args)
{
Context<Teacher> teachersContext = new Context<Teacher>(100);//contructs a db of 100 teachers
Context<Student> studentsContext = new Context<Student>(100);//contructs a db of 100 teachers
Repository repo = new Repository();
// set the repository context and get a teacher
repo.Context = teachersContext;
Teacher teacher1 = repo.Get<Teacher>(83); //get teacher number 83
Console.WriteLine("Teacher Id:{0} ", teacher1.Id);
// redirect the repositry context and now get a student
repo.Context = studentsContext;
Student student1 = repo.Get<Student>(35); //get student number 35
Console.WriteLine("Student Id: {0} ", student1.Id);
Console.ReadLine();
}
精彩评论