为什么C#out泛型类型参数违反协方差

本文关键字:方差 C#out 泛型类型参数 为什么 | 更新日期: 2023-09-27 18:20:45

我不清楚为什么下面的代码片段不是covarent?

  public interface IResourceColl<out T> : IEnumerable<T> where T : IResource {
    int Count { get; }
    T this[int index] { get; }
    bool TryGetValue( string SUID, out T obj ); // Error here?
    }

错误1无效差异:类型参数"T"必须是不变量在"IResourceColl.TryGetValue(string,out T)"上有效T’是协变。

我的接口只在输出位置使用模板参数。我可以很容易地将这些代码重构为类似的代码

  public interface IResourceColl<out T> : IEnumerable<T> where T : class, IResource {
    int Count { get; }
    T this[int index] { get; }
    T TryGetValue( string SUID ); // return null if not found
    }

但我试图理解我的原始代码是否真的违反了协方差,或者这是编译器还是.NET对协方差的限制。

为什么C#out泛型类型参数违反协方差

问题确实在这里:

bool TryGetValue( string SUID, out T obj ); // Error here?

您将obj标记为out参数,这仍然意味着您正在传入obj,因此它不能是协变的,因为您既传入T类型的实例又返回它。

编辑:

Eric Lippert说,这比任何人都好,我引用了他对"C#中的ref和out参数,不能标记为变量"的回答,并引用了他关于out参数的话:

将T标记为"out"是否合法?遗憾的是没有。"出局"实际上和幕后的"裁判"没什么不同。唯一"out"answers"ref"的区别在于编译器禁止在被调用者分配out参数之前读取该参数,以及编译器在被调用者返回之前需要赋值正常情况下在C#以外的.NET语言将能够在它已初始化,因此可以用作输入。我们因此,在这种情况下禁止将T标记为"out"。很遗憾,但我们对此无能为力;我们必须遵守型号安全规则CLR的。

以下是使用扩展方法的可能解决方法。从实现者的角度来看不一定很方便,但用户应该很高兴:

public interface IExample<out T>
{
    T TryGetByName(string name, out bool success);
}
public static class HelperClass
{
    public static bool TryGetByName<T>(this IExample<T> @this, string name, out T child)
    {
        bool success;
        child = @this.TryGetByName(name, out success);
        return success;
    }
}
public interface IAnimal { };
public interface IFish : IAnimal { };
public class XavierTheFish : IFish { };
public class Aquarium : IExample<IFish>
{
    public IFish TryGetByName(string name, out bool success)
    {
        if (name == "Xavier")
        {
            success = true;
            return new XavierTheFish();
        }
        else
        {
            success = false;
            return null;
        }
    }
}
public static class Test
{
    public static void Main()
    {
        var aquarium = new Aquarium();
        IAnimal child;
        if (aquarium.TryGetByName("Xavier", out child))
        {
            Console.WriteLine(child);
        }
    }
}

它违反了协方差,因为提供给输出参数的值必须与输出参数声明的类型完全相同。例如,假设T是一个字符串,协方差意味着可以进行

var someIResourceColl = new someIResourceCollClass<String>();
Object k;
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String

检查这个小例子,你就会明白为什么不允许:

public void Test()
{
    string s = "Hello";
    Foo(out s);
}
public void Foo(out string s) //s is passed with "Hello" even if not usable
{
    s = "Bye";
}

out意味着在执行离开方法之前必须明确地分配s,相反,在方法体中明确地分配之前不能使用s。这似乎与协方差规则兼容。但是,在调用该方法之前,没有什么可以阻止您在调用站点分配s。该值被传递给方法,这意味着即使它不可用,您也会有效地将定义类型的参数传递给该方法,这违反了协方差的规则,即泛型类型只能用作方法的返回类型。