可以传递不带类型参数的泛型委托吗?
本文关键字:泛型 类型参数 | 更新日期: 2023-09-27 18:05:32
我有三个项目
- MVC Web应用程序
- 两层业务/存储库的服务应用
- 实体框架(所有EF配置都在这里)
MVC references> service
Service references> EF
我有这三个方法目前做一些工作。
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 { }
我从我的控制器调用这三个方法,使用这些委托给上面的工作方法的接口:
Boolean StoreUpload(UploadInformation information);
Boolean RemoveUpload(UploadInformation information);
Boolean CommitStoredDocuments(UploadInformation information);
基于交换机中UploadTypes枚举中的条件,我调用正确的工作方法。我这样做是因为我不希望我的mvc项目访问EF数据库类型,否则我知道有人会开始从整个应用程序查询数据。我将这些switch语句用于所有的接口方法:
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) { ... }
这个方法可能会稍微说明使用类型参数的目的。我正在使用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);
...
}
如果能够将需要类型参数的工作方法作为委托传递给切换工作方法调用就好了。
public bool DoSomeWork(delegateMethod, information) {
switch(information.Type) {
case UploadTypes.AutoInciden:
return delegateMethod<AutoIncident>(information);
...
}
}
这能做到吗?此外,我在为这个问题构建一个好的标题时遇到了麻烦,所以如果这些是描述挑战的更好方式,请评论。
由于以下几个原因,这不能直接做到。
首先,正如您可能注意到的,delegateMethod<FooBar>(information)
根本无法编译。这是因为在您的示例中,delegateMethod
是一个局部变量(实际上是方法参数,但仍然是一个变量),并且您不能将"类型参数"<FooBar>
应用于变量-您只能将它们应用于指示(泛型)类型或(泛型)方法的标识符。
第二个原因更有趣。当你将一个方法作为委托传递时,委托实际上捕获了整个方法签名,包括所有参数类型。
void Blah<T>(UploadInformation information){ ... }
var one = new Action<int>(Blah); // -> Blah<int>
var two = new Action<float>(Blah); // -> Blah<float>
var thr = new Action<andsoon>(Blah); // -> Blah<andsoon>
MagicDoSomeWork(one, ...); // these all
MagicDoSomeWork(two, ...); // delegates are already bound
MagicDoSomeWork(thr, ...); // and remember their concrete T
您需要实际指定Action
的类型,以便从称为Blah
的一般描述中选择适当版本的泛型方法。这些委托绑定到方法的具体版本,并且只接受该类型。就其类型参数而言,这些委托是"封闭的"。使用正常的方式,MagicDoSomeWork
将根本没有办法改变这些委托已经记住的T
。
这两件事是一种显示停止,因为只有通过正常代码,你不能写像
这样的东西var nope1 = new Action(Blah); // ctor for Action NEEDS type parameter
,因为Action构造函数只需要一个类型参数。一旦你传入任何参数,它就会锁定Blah类型参数
也不能使用open委托:
var nope1 = new Action<>(Blah); // can't use empty <> in this context :(
因为new
操作符需要一个完整类型来创建对象。
然而,通过一些反射,动态地分析和构建泛型类型或泛型方法是可能的。
// first, build the delegate in a normal way
// and pick anything as the type parameters
// we will later replace them
var delegateWithNoType = new Action<object>(Blah);
// delegate has captured the methodinfo,
// but uses a stub type parameter - it's useless to call it
// but it REMEMBERS the method!
// .... pass the delegate around
// later, elsewhere, determine the type you want to use
Type myRealArgument;
switch(..oversomething..)
{
default: throw new NotImplemented("Ooops");
case ...: myRealArgument = typeof(UploadTypes.AutoIncident); break;
...
}
// look at the delegate definition
var minfo = delegateWithNoType.Method;
var target = delegateWithNoType.Target; // probably NULL since you cross layers
var gdef = minfo.GetGenericDefinition();
var newinfo = gdef.MakeGenericMethod( myRealArgument );
// now you have a new MethodInfo object that is bound to Blah method
// using the 'real argument' type as first generic parameter
// By using the new methodinfo and original target, you could now build
// an updated delegate object and use it instead the original "untyped" one
// That would be a NEW delegate object. You can't modify the original one.
// ...but since you want to call the method, why don't use the methodinfo
UploadInformation upinfo = ... ;
newinfo.Invoke(target, new object[] { upinfo });
// -> will call Blah<UploadTypes.AutoInciden>(upinfo)
警告字:这是一个草图,向您展示delegate.Method/Target
和methodinfo
以及getgenericdefinition
和makegenericmethod
如何工作。我凭记忆写的,从来没有编译过,也从来没有运行过。它可能包含小的错别字,被忽视的东西和看不见的彩虹独角兽。我没有注意到。可能是因为它们是隐形的
你可以这样做
public bool Invoke(EntityType entityType, ActionType action, Object[] arguments)
{
var actionType = Enum.GetName(typeof(ActionType), action);
var type = GetType();
var method = type.GetMethods().Single(m => m.IsGenericMethod && m.Name == actionType);
switch (entityType)
{
case EntityType.IncidentInjury:
var genericMethod = method.MakeGenericMethod(typeof(IncidentInjury));
return (bool)genericMethod.Invoke(this, arguments);
default:
return false;
}
}
枚举将只是我想以这种方式调用的方法的列表,我为我的服务创建了一个基类,所以我不必将实例传递给invoke方法。
考虑使用接口(或抽象类)代替委托。这样,你的方法就可以保留它们的泛型性质。
例如,如果您创建了如下接口:
interface IUploadAction
{
bool Perform<T>(UploadInformation information)
where T : class, IUploadEntity;
}
注意,T
没有在类型中公开,它只在方法上。这是关键部分。
现在你可以为你的数据库方法实现这个:
class CommitStoredDocuments : IUploadAction
{
public bool Perform<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);
//...
}
}
你的切换/调度方法可以像这样:
public bool DoAction(IUploadAction action, UploadInformation information)
{
switch (information.Type)
{
case UploadTypes.AutoIncident:
return action.Perform<AutoIncident>(information);
case UploadTypes.Incident:
return action.Perform<IncidentInjury>(information);
case UploadTypes.Inspection:
return action.Perform<Inspection>(information);
case UploadTypes.OtherIncident:
return action.Perform<OtherIncident>(information);
default:
return false;
}
}
然后你可以这样写:
IUploadAction storeUpload;
public bool StoreUpload(UploadInformation information) => DoAction(storeUpload, information);