13

我不清楚为什么以下代码片段不是协变的?

  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 对协方差的限制。

4

4 回答 4

14

问题确实在这里:

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

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

编辑:

Eric Lippert 说得比我提到他对“C# 中的 ref 和 out 参数并且不能标记为变体”的答案的任何人都好,并在参数方面引用了他的话out

将 T 标记为“out”是否合法?抱歉不行。“out”实际上与幕后的“ref”没有什么不同。“out”和“ref”的唯一区别是编译器禁止在被调用者分配之前从out参数读取,并且编译器要求在被调用者正常返回之前进行赋值。使用 C# 以外的 .NET 语言编写此接口的实现的人将能够在项目初始化之前读取它,因此它可以用作输入。因此,在这种情况下,我们禁止将 T 标记为“out”。这很遗憾,但我们无能为力;我们必须遵守 CLR 的类型安全规则。

于 2012-01-18T16:45:53.210 回答
4

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

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);
        }
    }
}
于 2014-10-10T12:54:22.133 回答
1

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

var someIResourceColl = new someIResourceCollClass<String>();
Object k;
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String
于 2012-01-18T16:48:25.070 回答
1

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

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在调用该方法之前在调用站点进行分配。该值被传递给方法,这意味着即使它不可,您实际上也将定义类型的参数传递给方法,这违反了协变规则,即泛型类型只能用作返回值方法的类型。

于 2012-01-18T17:15:11.900 回答