【问题标题】:Can you pass generic delegate without the type parameter?您可以在没有类型参数的情况下传递泛型委托吗?
【发布时间】:2016-09-04 19:15:39
【问题描述】:

我有三个项目

  1. MVC Web 应用程序
  2. 一种两层业务/存储库的服务应用程序
  3. 实体框架(所有 EF 配置都在这里)

MVC参考>服务

服务参考 > 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);
         ...
    }
} 

这可以吗? 另外,我在为这个问题构建一个好的标题时遇到了麻烦,所以如果这些是描述挑战的更好方式,请发表评论。

【问题讨论】:

  • 如果DoSomeWork 方法已经拥有对正确方法的引用(使用delegateMethod),为什么还要再次使用switch 子句?
  • 我从无权访问这些类型的层调用。

标签: c# asp.net-mvc entity-framework generics delegates


【解决方案1】:

由于多种原因,无法直接完成。

首先,您可能已经注意到,delegateMethod&lt;FooBar&gt;(information) 根本无法编译。这是因为在您的示例中,delegateMethod 是一个局部变量(实际上是方法参数,但仍然是一个变量),并且您不能将“类型参数”&lt;FooBar&gt; 应用于变量 - 您只能将它们应用于指示的标识符(通用)类型或(通用)方法。

第二个原因更有趣。当您将方法作为委托传递时,委托实际上会捕获整个方法签名,包括所有参数类型。

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 类型的参数

您也不能使用开放委托:

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/Targetmethodinfogetgenericdefinitionmakegenericmethod 如何工作的草图。我是凭记忆写的,从未编译过,从未运行过。它可能包含轻微的拼写错误、被忽视的东西和看不见的彩虹独角兽。我没有注意到。可能是因为它们是隐形的。

【讨论】:

  • 谢谢,我就是这么想的。它将计算出代码。这似乎是应对这一挑战的解决方案。我有这个解决方案,但想先知道。
  • @jwize:我稍微编辑了文本,在一些地方使用了你的类型名,应该更容易阅读。顺便说一句-您选择的标题很好。在这种情况下传递代表是很棘手的。我忘了写的是你实际上可能不需要委托,特别是如果你最终得到null 目标。对于未来的维护者来说,传递“裸”的 MethodInfos 可能更容易理解。 (续)
  • @jwize: 你仍然可以使用 Action/Func 之类的委托来捕获方法,然后当你将它们传递到另一层时,你可以选择它们的 .Method,然后获取 .GetGenericDefinition 并传递 那个到另一层。您将传递开放的泛型方法声明,但尚未填充类型参数。对于未来的读者来说,这将是一个非常明确的“不要使用我”的标志。但是,它看起来比将 Action 传递到下面的层更复杂。功能相同(除非 .Target 为 != null),因此请选择您更喜欢的任何内容。
【解决方案2】:

你可以这样做

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 方法。

【讨论】:

    【解决方案3】:

    考虑使用接口(或抽象类),而不是使用委托。这样,您的方法就可以保留它们的通用性。

    例如,如果您创建如下界面:

    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);
    

    【讨论】:

    • 对不起,我不确定我是否关注这个?然后我必须为每个实现创建一个新的开关,这是我试图避免的。您的代码也需要仔细查看。
    • @jwize 我误解了问题所在,我会更新我的答案...这有帮助吗?
    • 是的,我就是这样做的。
    猜你喜欢
    • 1970-01-01
    • 2022-10-13
    • 1970-01-01
    • 2021-10-18
    • 2023-03-28
    • 1970-01-01
    • 2018-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多