16

这是一个有点长的问题,所以请多多包涵。

我需要在一组字符串和每个字符串的相应通用方法调用之间创建一个映射。但是我遇到了一个编译问题,下面解释一下。

在我的场景中,我使用的是Dictionary<>,但问题同样存在于List<>. 为简单起见,我List<>在下面的示例中使用 a 。

考虑这三个类:

public abstract class MyBase { /* body omitted */  }
public class MyDerived1 : MyBase { /* body omitted */  }
public class MyDerived2 : MyBase { /* body omitted */  }

以及其他一些类中的方法:

public class Test
{
    public T GetT<T>() where T : MyBase { /* body omitted */ }
}

在另一个类中,我可以List<Func<MyBase>>这样声明:

public class SomeClass
{
    public void SomeFunc()
    {
        var test = new Test();

        var list1 = new List<Func<MyBase>>
            {
                test.GetT<MyDerived1>,
                test.GetT<MyDerived2>
            };
    }
}

这一切都很好。

但是,如果我想要一个返回这样的泛型类的函数怎么办:

public class RetVal<T> where T : MyBase { /* body omitted */ }

public class Test
{
    public RetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

我想List<>使用这个函数创建一个等价物。即一个列表>>?

public class Class1
{
    public void SomeFunc()
    {
        var test = new Test();

        var list2 = new List<Func<RetVal<MyBase>>>
            {
                test.GetRetValT<MyDerived1>, // compile error
                test.GetRetValT<MyDerived2> // compile error
            };
    }
}

我得到Expected a method with 'RetVal<MyBase> GetRetValT()' signature.

那么,有什么办法可以解决这个问题,还是有另一种方法可以用来创建我的字符串...通用方法调用映射?

4

4 回答 4

16

C# 只允许接口上的协方差。这意味着您不能自动将 aRetVal<MyDerived1>转换为 a RetVal<MyBase>。如果RetVal应该是协变的,请为其创建一个接口,如下所示:

public interface IRetVal<out T>
{

}
public class RetVal<T> : IRetVal<T> where T : MyBase { /* body omitted */ }

public class Test
{
    public IRetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

然后这段代码将起作用:

    var list2 = new List<Func<IRetVal<MyBase>>>
        {
            test.GetRetValT<MyDerived1>,
            test.GetRetValT<MyDerived2>
        };
于 2012-05-11T15:48:37.123 回答
7

问题是泛型的经典协变/逆变。您假设因为MyDerived1MyDerived2继承自MyBase,aRetVal<MyDerived1>继承自RetVal<MyBase>,但事实并非如此。

解决此问题的最简单方法可能是将代码更改为:

var list2 = new List<Func<RetVal<MyBase>>>
        {
            () => (MyBase)test.GetRetValT<MyDerived1>,
            () => (MyBase)test.GetRetValT<MyDerived2>
        };

或者更好的是,正如 JS 在评论中指出的那样,RetVal<T>如果可能的话,只需更改为协变:

public interface IRetVal<out T> { ... }

public class RetVal<T> : IRetVal<T> { ... }
于 2012-05-11T15:46:33.657 回答
3

类的泛型类型参数不能是协变的,但它们可以是接口的协变的。如果类型参数被声明为协变,您可以使用 interfaceIRetVal<T>而不是 class来做您想做的事情。 为了将接口类型参数声明为协变,它必须仅用于“输出”位置。RetVal<T>

为了说明,这段代码不会编译:

interface IRetVal<out T>
{
    T Value { get; }
    void AcceptValue(T value);
}

要编译代码,您必须out从类型参数中删除修饰符,或者删除 AcceptValue 方法(因为它使用 T 作为参数:输入位置)。

使用该IRetVal<out T>界面,您可以执行以下操作:

public class MyBase { }
public class MyDerived1 : MyBase { }
public class MyDerived2 : MyBase { }

public interface IRetVal<out T> where T : MyBase { /* body omitted */ }

public class Test
{
    public IRetVal<T> GetRetValT<T>() where T : MyBase
    {
        return null;
    }
}

public class Class1
{
    public void SomeFunc()
    {
        var test = new Test();

         var list = new List<Func<IRetVal<MyBase>>> 
        { 
            test.GetRetValT<MyDerived1>,
            test.GetRetValT<MyDerived2>
        };
    }
}
于 2012-05-11T16:01:31.463 回答
2

我最近有一个类似的问题。

C# 不支持用于接口实现或虚拟方法覆盖的返回类型协变。有关详细信息,请参阅此问题:

C# 是否支持返回类型协方差?.

你也许可以破解这个,我的做法是:

public RetVal<R> GetRetValT<T,R>() where T : MyBase where R : MyBase
{
    return null;
}

//Then, change this to:
test.GetRetValT<MyDerived1, MyBase>
于 2012-05-11T15:46:26.403 回答