你可以在没有type参数的情况下传递泛型委托吗?

时间:2016-09-04 19:15:39

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

我有三个项目

  1. MVC Web应用程序
  2. 服务应用程序,是一种两层业务/存储库
  3. 实体框架(所有EF配置都在这里)
  4. 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);
             ...
        }
    } 
    

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

3 个答案:

答案 0 :(得分:3)

由于几个原因,不能直接完成。

首先,您可能已经注意到,delegateMethod<FooBar>(information)根本就没有编译。这是因为在您的示例中,delegateMethod是局部变量(实际上是方法参数,但仍然是变量),并且您不能应用&#34;类型参数&#34; <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的一般描述中选择适当版本的泛型方法。这些委托绑定到方法的具体版本,并且只接受这些类型。这些代表已经关闭了#39;就他们的类型参数而言。使用常规方式,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运算符需要完整类型才能创建对象。

然而,通过一些反思voodoo,可以动态地分析和构建泛型类型或泛型方法。

// 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/Targetmethodinfo以及getgenericdefinitionmakegenericmethod的工作原理。我是从内存中编写的,从未编译过,从未运行过。它可能包含轻微的错别字,被忽视的东西和看不见的彩虹独角兽。我没有注意到。可能是因为它们不可见。

答案 1 :(得分:0)

你可以这样做

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

答案 2 :(得分:0)

不要使用委托,而应考虑使用接口(或抽象类)。这样,您的方法可以保留其通用性质。

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

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