绑定通用方法委托时出错 - 签名或安全透明度

时间:2016-09-07 20:58:56

标签: c# generics reflection delegates func

我使用反射来做一些数据库查询,并且在创建委托时加快了反射的使用,我偶然发现了以下错误:

  

无法绑定到目标方法,因为它的签名或安全性   透明度与委托类型的透明度不兼容。

我有两种创建这些委托的方法,两种方法都具有相似的结构,但是一种方法有效,另一种无效。它们之间唯一的主要区别是,不工作的那个有更多的参数并返回一个具有泛型类型的List,其中工作的只接受一个参数并返回一个声明类型的单个值而不是一个通用T.

以下是一些示例代码:

方法

public List<T> GetConnections<T>(IElement element, bool getChildren, bool getParents) where T : IConnectionTable, new()
{
    // do some database stuff and return a List<T> where the constraints on
    // T follow the method description above.
}

创建代理

为简洁起见,简化

private Func<IElement, bool, bool, List<IConnectionTable>> GetConnectionsDelegate(string connectionType)
{
    // Get the type element from the passed string.
    Type elementType = Type.GetType(connectionType, true);

    // Create the generic method using that type.
    MethodInfo method = typeof(MyClass).GetMethod("GetConnections", new Type[]{ typeof(IElement), typeof(bool), typeof(bool) });
    MethodInfo generic = method.MakeGenericMethod(elementType);

    // Create a delegate of the method to speed up subsequent queries.
    var converted = (Func<IElement, bool, bool, List<IConnectionTable>>)Delegate.CreateDelegate(typeof(Func<IElement, bool, bool, List<IConnectionTable>>), this, generic);

    // the above line is where it dies
}

实际代码将委托保存到私有静态字典中,因此我只需要使用一次反射。

如果我打印出方法泛型的内容,它们似乎都被正确转换。

Result StandardOutput:  

System.Collections.Generic.List`1[T] GetConnections[T](MyProject.Database.IElement, Boolean, Boolean)
System.Collections.Generic.List`1[MyTestProject.TestConnection] GetConnections[TestConnection](MyProject.Database.IElement, Boolean, Boolean)

我的假设是问题在于通用List与IConnectionTable List返回类型之间的区别,但是使方法返回非泛型列表会导致泛型方法中出现大量的强制转换错误,这样做有点不对无论如何。此外,该代码在测试时运行良好。

它不应该是私有方法和公共方法之间的区别,因为我的其他委托创建方法是相同的并且工作正常(我也尝试将 GetConnectionsDelegate 更改为公共和没有区别。)

非常感谢任何帮助。

NDH

1 个答案:

答案 0 :(得分:3)

您遇到困难的原因实际上与您的通用调用无关,而是与您的返回类型无关。泛型类不支持协方差,这实际上是您在使用实现List<IConnectionTable>的具体类型时返回IConnectionTable所要完成的。

解决方法是使用协变接口集合,例如IEnumerable<T>。此外,您需要通过第二个参数添加实际实例,因为this可能指向不正确的上下文。

var converted = (Func<IElement, bool, bool, IEnumerable<IConnectionTable>>)
    Delegate.CreateDelegate(typeo‌​f(Func<IElement, bool, bool, IEnumerable<IConnectionTable>>), new MyClass(), generic);

此外,您可能需要考虑编译的表达式而不是委托,因为它们可以很好地适应您的情况,并为您提供更多的可读性和更大的灵活性。您需要分析每种方法并确定哪种方法表现更好。

以下是一个简单的缓存编译表达式示例,它正确地从编译表达式输出“我们被调用”。

void Main()
{
    var key = typeof(ConnectionTypeOne).FullName;
    Func<IElement, bool, bool, IEnumerable<IConnectionTable>> expr = 
        _cache.ContainsKey(key) ? _cache[key]
        : CreateConnectionExpression<ConnectionTypeOne>(key);

    expr(new Element(), true, true);
}

private static IDictionary<string, Func<IElement, bool, bool, IEnumerable<IConnectionTable>>> _cache = 
    new Dictionary<string, Func<IElement, bool, bool, IEnumerable<IConnectionTable>>>();

private Func<IElement, bool, bool, IEnumerable<IConnectionTable>> CreateConnectionExpression<T>(string connectionType)
    where T : IConnectionTable
{
    // Get the type element from the passed string.
    Type elementType = Type.GetType(connectionType, true);

    // Create the generic method using that type.
    MethodInfo method = typeof(MyClass).GetMethod("GetConnections", new Type[] { typeof(IElement), typeof(bool), typeof(bool) });
    MethodInfo generic = method.MakeGenericMethod(elementType);

    var instance = Expression.Constant(new MyClass());
    var c1 = Expression.Parameter(typeof(IElement));
    var c2 = Expression.Parameter(typeof(bool));
    var c3 = Expression.Parameter(typeof(bool));

    var expr = Expression.Call(instance, generic, c1, c2, c3);
    Func<IElement, bool, bool, IEnumerable<IConnectionTable>> compiledExpr =
        (Func<IElement, bool, bool, IEnumerable<IConnectionTable>>)
            Expression.Lambda(expr, c1, c2, c3).Compile();

    _cache[connectionType] = compiledExpr;

    return compiledExpr;
}

public class MyClass 
{
    public List<T> GetConnections<T>(IElement element, bool getChildren, bool getParents) 
        where T : IConnectionTable, new()
    {
        Console.WriteLine("we got called");
        return new List<T>();
    }
}

public interface IElement { }
public interface IConnectionTable { }

public class Element : IElement { }
public class ConnectionTypeOne : IConnectionTable { }