2012-01-18 134 views
10

我不清楚为什么下面的代码段不是covarient?为什么C#out泛型类型参数违反协方差?

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(字符串,出T)' 不变地 有效。 'T'是 covariant。

我的界面只在输出位置使用模板参数。我可以很容易地重构这个代码,以类似

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的限制。

+0

的可能重复[C#:为什么不呢?做“裁判”和“出”支持多态性(HTTP://计算器。 COM /问题/ 1207144/C锋利为什么-犯规,裁判和出支持多态) – Jon 2012-01-18 16:43:37

+0

这里要注意的重要一点是,['out'](http://msdn.microsoft.com/en -us /库/ ee332485.aspx)(参数改性剂)是完全无关的(http://msdn.microsoft.com/en-us/library/dd469487.aspx)(使用[out'']在通用型参数)。 – Jon 2012-01-18 16:44:50

+0

@Jon - 这个问题适用于C#3.0和之前的版本。这里描述的语法是C#4.0 – Oded 2012-01-18 16:44:57

回答

11

的问题确实是在这里:

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

你标记的obj为out参数,这仍然意味着尽管这你在obj传递所以它不可能是协变的,因为你们俩传中的实例键入T并返回。

编辑:

埃里克利珀说,它比任何人我指的是his answer to "ref and out parameters in C# and cannot be marked as variant"并引述他的问候out参数更好:

它应该是合法的,将T标记为“走出去” ?很不幸的是,不行。 “out” 实际上与幕后的“ref”没有区别。 “out”和“ref”之间唯一的 区别在于编译器禁止 在被调用者分配之前从out参数中读取数据,并且 在调用者正常返回 之前编译器需要分配。 有人在C#之外的其他.NET语言中编写了此接口的实现,他们可以在 初始化之前读取该项目,因此它可以用作输入。因此,我们 禁止在此情况下将T标记为“出”。这很遗憾, 但我们无能为力;我们必须遵守CLR的类型安全规则 。

+0

我可能会传入除T之外的其他内容,但C#规则要求我在分配内容之前先指定一些内容,这样它永远不会导致问题。 – MerickOWA 2012-01-18 17:00:41

+0

啊我现在看到,有可能以其他语言读取价值,而eric的博客甚至在C#中指出可能会破坏的情况。这就解释了为什么协变违反了。 – MerickOWA 2012-01-18 17:25:25

+0

问题是,所谓的'out'参数确实不是;一个真实的'out'参数会导致编译器为函数的返回生成一个结构类型,这将包括指定的返回类型和所有'out'参数;调用者会自动将结构中的相应字段复制到out参数中,然后将剩余字段(如果有)作为返回值。如果以这种方式实施参数,它们确实可以是协变的。 – supercat 2012-06-28 18:08:53

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 
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。该值被传递给这意味着该方法,即使它不是可用你有效地传递一个定义类型的方法的参数,这违背协方差的规则,其中指出泛型类型只能是用作方法的返回类型。

3

下面是一个使用扩展方法的可行的解决方法。不一定是从的观点实施者点方便,但用户应该感到高兴:

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