几年后回到C#,所以我有点生锈。遇到了这个(简化的)代码,这让我无所适从。
为什么必须显式实现IDataItem.Children
属性?普通的Children
属性不能满足要求吗?毕竟,该属性直接用于满足它。为什么它不是隐式的?
public interface IDataItem {
IEnumerable<string> Children { get; }
}
public class DataItem : IDataItem {
public Collection<string> Children { get; } = new Collection<string>();
// Why doesn't 'Children' above implement this automatically?!
// After all it's used directly to satisfy the requirement!
IEnumerable<string> IDataItem.Children => Children;
}
根据C#源代码,这是Collection<T>
的定义:
[System.Runtime.InteropServices.ComVisible(false)]
public class Collection<T> :
System.Collections.Generic.ICollection<T>,
System.Collections.Generic.IEnumerable<T>, <-- Right Here
System.Collections.Generic.IList<T>,
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.Generic.IReadOnlyList<T>,
System.Collections.IList
如您所见,它显式实现了IEnumerable<T>
,据我了解,如果'X'实现了'Y',那么'X'是是'Y',所以{{1 }} 是Collection<String>
,所以我不确定为什么不满意。
答案 0 :(得分:6)
也许这个例子更清楚了。我们希望签名完全匹配 1,2 ,尽管类型之间存在继承关系,也不允许替换。
我们不允许这样写:
public interface IDataItem {
void DoStuff(string value);
}
public class DataItem : IDataItem {
public void DoStuff(object value) { }
}
您的示例是相同的,除了要求返回类型而不是参数(出于显而易见的原因,并采用缩小而不是扩展转换)。但是,适用相同的原则。当涉及匹配签名时,类型必须完全匹配 3 。
您可以要求一种允许发生这种事情并且可能存在的语言。但是事实是,这些都是C#的规则。
1 除了对Co- and Contra-variance的某些有限支持之外,还涉及到泛型和接口/代理。
2 自从 以来,有人可能会争论签名是否是正确的词,返回类型与参数类型,泛型等重要。在其他人谈论C#方法签名的大多数其他情况下,它们将显式地忽略返回类型,因为它们(显式或隐式地)考虑了重载规则的含义,对于重载,返回类型不是签名的一部分。
尽管如此,我对这里使用“签名”一词感到满意。签名没有在C#规范中正式定义以及在哪里使用,通常是指出签名没有的哪些部分需要考虑重载。
3 更不用说如果您的Children
方法返回恰好实现struct
的{{1}}会引起的问题。现在,您已经有了一个返回值类型的值和调用方的方法(通过IEnumerable<string>
接口,该接口希望收到对对象的引用。
因此,该方法甚至不能按原样使用。在这种情况下,我们必须具有 hidden 装箱转换才能实现该接口。我相信,当C#的这一部分被指定时,他们试图使代码不具有太多“隐藏的魔术”,以便您可以轻松地编写自己的代码。
答案 1 :(得分:5)
在您的示例中,您的“普通” Children属性实际上不满足接口要求。类型不同。您可以投射它并不重要-它们是不同的。
类似的示例,也许更明显的是,如果您要使用返回IEnumerable的实际方法实现接口并尝试使用实际类中的ICollection方法。仍然存在编译时错误。
正如@Ben Voigt所说,该转换仍会生成一些代码,如果要拥有它-您需要隐式添加它。
答案 2 :(得分:0)
这里的问题已经回答了(您必须匹配接口类型),我们可以用一个例子来说明问题。如果可行:
public interface IDataItem {
IEnumerable<string> Children { get; set; }
void Wipe();
}
public class DataItem : IDataItem {
public Collection<string> Children { get; } = new Collection<string>();
public void Wipe() {
Children.ClearItems(); //Property exists on Collection, but not on IEnumerable
}
}
然后我们使用如下代码:
IDataItem x = new DataItem();
//Should be legal, as Queue implements IEnumerable. Note entirely sure
//how this would work, but this is the system you are asking about.
x.Children = new Queue<string>();
x.Wipe(); // Will fail, as Queue does not have a ClearItems method within
要么意味着该属性只能枚举,要么需要Collection类的属性-正确键入接口。
答案 3 :(得分:-1)
任何实现该接口的类都必须包含与该接口指定的签名匹配的方法的定义。该接口仅定义签名。这样,C#中的接口类似于其中所有方法都是抽象的抽象类。
接口可以包含方法,属性,事件,索引器或这四种成员类型的任意组合。
这里是有关接口的不错的阅读。 Interfaces in C#
希望它可以帮助您进一步理解。