I have three projects
- MVC Web application
- Service application which is kind of two layers business/repository
- Entity framework (all EF configuration lives here)
MVC references > service
Service references > EF
I have these three methods currently that do some work.
public bool StoreUpload<T>(UploadInformation information)
where T : class, IUploadEntity { }
public bool RemoveUpload<T>(UploadInformation information)
where T : class, IUploadEntity { }
public bool CommitUpload<T>(UploadInformation information)
where T : class, IUploadEntity { }
I call these three methods from my controller using these interfaces which delegate to the work methods above:
Boolean StoreUpload(UploadInformation information);
Boolean RemoveUpload(UploadInformation information);
Boolean CommitStoredDocuments(UploadInformation information);
Based on a condition from UploadTypes enumeration in a switch I call the correct work method. I do this because I don't want my mvc project to have access to the EF database types otherwise I know someone is going to start querying data from all over the application. I use these switch statements for all interfaced methods:
public bool StoreUpload(UploadInformation information)
{
switch (information.Type)
{
case UploadTypes.AutoIncident:
return RemoveUpload<AutoIncident>(information);
case UploadTypes.Incident:
return RemoveUpload<IncidentInjury>(information);
case UploadTypes.Inspection:
return RemoveUpload<Inspection>(information);
case UploadTypes.OtherIncident:
return RemoveUpload<OtherIncident>(information);
default:
return false;
}
}
public bool RemoveUpload(UploadInformation information) { ... }
public bool CommitStoredUpload(UploadInformation information) { ... }
This method might shed a little light on what the types parameters are being used for. I am updating tables in a generic way using EF.
private bool CommitStoredDocuments<T>(UploadInformation information) where T : class, IUploadEntity
{
var uploads = GetStoredUploads(information.UniqueId);
var entity = db.Set<T>().Include(e => e.Uploads)
.Single(e => e.UniqueId == information.UniqueId);
entity.Uploads.AddRange(uploads);
...
}
It would be nice to be able to pass the work method which requires a type parameter as a delegate to the switch work method calls.
public bool DoSomeWork(delegateMethod, information) {
switch(information.Type) {
case UploadTypes.AutoInciden:
return delegateMethod<AutoIncident>(information);
...
}
}
Can this be done? Also, I had trouble constructing a good title for this question so please comment if these is a better way to describe the challenge.
Instead of using delegates, consider using an interface (or abstract class). This way, your methods can retain their generic nature.
For example, if you create an interface like:
Note that the
T
is not exposed in the type, it's only on the method. This is the key part.Now you can implement this for your database methods:
Your switching/dispatching method can look like this:
And then you can write something like:
It cannot be done directly due to several reasons.
First of all, as you probably noticed,
delegateMethod<FooBar>(information)
simply does not compile. This is because in your example thedelegateMethod
is a local variable (method parameter actually, but still a variable), and you cannot apply "type arguments"<FooBar>
to a variable - you can apply them only on an identifier that indicates a (generic) type or a (generic) method.Second reason is more interesting. When you pass a method as a delegate, the delegate actually catches the whole method signature, including all parameter types.
You need to actually specify the type for the
Action
so a proper version of generic method will be picked from a general description calledBlah
. These delegates are bound to concrete versions of the method and will accept only that types. These delegates are 'closed' in terms of their type arguments. Using normal ways, theMagicDoSomeWork
will simply have no way of altering theT
which these delegates already have remembered.That two things are a kind of show stoppers, since by normal code only, you cannot write things like
since Action constructor simply requires a type parameter. And once you pass any, it will lock the Blah type arguments
Also you cannot use open delegates:
since
new
operator requires a full type to create an object.However, with a bit of reflection voodoo, it is possible to analyze and build a generic type or a generic method dynamically.
word of warning: this is a sketch to show you how the
delegate.Method/Target
andmethodinfo
andgetgenericdefinition
andmakegenericmethod
work. I wrote it from memory, never compiled, never ran. It can contain minor typos, overlooked things and invisible rainbow unicorns. I didn't noticed any. Probably because they were invisible.You can do it like this
The enum will just be a list of methods that I want to invoke this way and I create a base class for my services so I don't have to pass the instance to the Invoke method.