以下代码:
public interface ISomeData
{
IEnumerable<string> Data { get; }
}
public class MyData : ISomeData
{
private List<string> m_MyData = new List<string>();
public List<string> Data { get { return m_MyData; } }
}
产生以下错误:
错误CS0738:'InheritanceTest.MyData' 没有实现接口成员 'InheritanceTest.ISomeData.Data'。 'InheritanceTest.MyData.Data'不能 实行 'InheritanceTest.ISomeData.Data' 因为它没有匹配 返回类型 'System.Collections.Generic.IEnumerable'。
由于List&lt; T&gt;实现IEnumerable&lt; T&gt;,可以认为我的类将实现该接口。有人可以解释一下这个不编译的理由是什么吗?
我可以看到,有两种可能的解决方案:
现在假设我还有以下代码:
public class ConsumerA
{
static void IterateOverCollection(ISomeData data)
{
foreach (string prop in data.MyData)
{
/*do stuff*/
}
}
}
public class ConsumerB
{
static void RandomAccess(MyData data)
{
data.Data[1] = "this line is invalid if MyPropList return an IEnumerable<string>";
}
}
我可以更改我的接口以要求实现IList(选项1),但这限制了谁可以实现接口以及可以传递给ConsumerA的类的数量。或者,我可以更改实现(类MyData),以便它返回IEnumerable而不是List(选项2),但然后必须重写ConsumerB。
这似乎是C#的一个缺点,除非有人能够启发我。
答案 0 :(得分:24)
对于你想要做的事情,你可能希望用一个返回List而不是IEnumerable的类(不是接口)成员显式地实现接口......
public class MyData : ISomeData
{
private List<string> m_MyData = new List<string>();
public List<string> Data
{
get
{
return m_MyData;
}
}
#region ISomeData Members
IEnumerable<string> ISomeData.Data
{
get
{
return Data.AsEnumerable<string>();
}
}
#endregion
}
编辑:为了澄清,这使MyData类在被视为MyData的实例时返回List;同时仍然允许它在被视为ISomeData的实例时返回IEnumerable的实例。
答案 1 :(得分:23)
不幸的是,返回类型必须匹配。您正在寻找的是“返回类型协方差”,C#不支持这一点。
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=90909
C#Compiler团队的高级开发人员Eric Lippert在他的博客中提到他们不打算支持返回类型的协方差。
“这种差异被称为 “返回型协方差”。就像我一样 在本系列的早期提到,(a) 这个系列不是那种 差异,(b)我们没有计划 在C#中实现这种差异。 “
值得阅读Eric关于协方差和逆变的文章。
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
答案 2 :(得分:4)
如果通过ISomeData接口访问MyData对象怎么办? 在这种情况下,IEnumerable可能是一个不能分配给List的基础类型。
IEnumerable<string> iss = null;
List<string> ss = iss; //compiler error
编辑:
我理解你的意见是什么意思。
无论如何,在你的情况下我会做的是:
public interface ISomeData<T> where T: IEnumerable<string>
{
T Data { get; }
}
public class MyData : ISomeData<List<string>>
{
private List<string> m_MyData = new List<string>();
public List<string> Data { get { return m_MyData; } }
}
使用适当的约束条件转换为通用接口我认为灵活性和可读性都是最好的。
答案 3 :(得分:3)
会员的签名不能有所不同。
您仍然可以在List<string>
方法中返回get
,但签名必须与界面相同。
简单地改变:
public List<string> Data { get { return m_MyData; } }
到
public IEnumerable<string> Data { get { return m_MyData; } }
关于您的其他选项:更改界面以返回List
。这应该避免。它很差encapsulation,被视为code smell。
答案 4 :(得分:1)
接口要求方法的签名与合同的签名完全匹配。
这是一个更简单的例子,也不会编译:
interface IFoo
{
Object GetFoo();
}
class Foo : IFoo
{
public String GetFoo() { return ""; }
}
现在关于如何处理它我会让界面决定实现。如果您希望合同为IEnumerable<T>
,那么这就是它在课堂上应该是什么。界面是最重要的,因为实现可以随心所欲地灵活实现。
请确保IEnumerable<T>
是最佳选择。 (这一切都非常主观,因为我对你的域名或你申请中这些类型的目的不太了解。祝你好运!)
答案 5 :(得分:1)
为什么不从界面返回一个List ...
public interface ISomeData
{
List<string> Data { get; }
}
如果您知道您的消费者将同时迭代它(IEnumerable
)并添加到它(IList
),那么简单地返回List<>
似乎是合乎逻辑的。
答案 6 :(得分:0)
如果您更改了接口以扩展IEnumerable,那么您可以枚举对象并通过类属性编辑数据。
public interface ISomeData : IEnumerable<string>
{
IEnumerable<string> Data { get; }
}
public class MyData : ISomeData
{
private List<string> m_MyData = new List<string>();
public List<string> Data { get { return m_MyData; }
public IEnumerator<string> GetEnumerator()
{
return Data;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
答案 7 :(得分:0)
您可以这样实现:
public class MyData : ISomeData
{
private List<string> m_MyData = new List<string>();
public IEnumerable<string> Data { get { return m_MyData; } }
}
答案 8 :(得分:0)
无论哪种方式,我都会像darin的回答一样解决它,或者,如果你明确地想要一个List访问器,你可以这样做:
public class MyData : ISomeData
{
IEnumerable<string> ISomeData.Data
{
get
{
return _myData;
}
}
public List<string> Data
{
get
{
return (List<string>)((ISomeData)this).Data;
}
}
}
答案 9 :(得分:0)
我会选择选项2:
在代码中定义接口的关键是定义合同,因此您和实现接口的其他人知道要达成一致意见。无论您在界面中定义IEnumerable还是List,都是合同问题,属于框架设计准则。这是一个whole book来讨论这个问题。
就个人而言,我会公开IEnumerable并将MyData实现为IEnumerable,你可以在RandomAccess()方法中将它强制转换回List。
答案 10 :(得分:0)
如果需要在您的界面中有一个列表(已知的具有随机访问权限的项目数),则应考虑将界面更改为
public interface ISomeData
{
ICollection<string> Data { get; }
}
这将为您提供列表中所需的可扩展性和功能。
List<T>
无法轻松进行子类化,这意味着您可能无法从想要实现界面的所有类中返回完全类型。
ICollection<T>
可以通过各种方式实施。
答案 11 :(得分:0)
我遇到了类似的情况,并希望提供一个更具体的例子,说明为什么不允许这样做。我的应用程序的通用部分处理包含属性的接口,并通过该接口向应用程序的另一部分提供数据,该部分包含实现这些接口的具体类。
我认为与您的目标类似:具有更多功能的conrete类,以及界面提供应用程序通用部分所需的绝对最小值。接口公开所需的最少功能是一种很好的做法,因为它可以最大限度地提高兼容性/可重用性。即它不需要接口的实现者实现超出需要的程度。
但请考虑您是否在该属性上设置了一个setter。您可以创建具体类的实例,并将其传递给采用ISomeData的通用帮助程序。在该助手的代码中,他们正在使用ISomeData。如果没有任何类型的通用T,其中T:new()或工厂模式,他们无法创建新的实例来进入与您的具体实现相匹配的数据。他们只返回一个实现IEnumerable的列表:
instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable();
如果SomeOtherTypeImplementingIEnumerable未继承List,但您已将.Data实现为List,那么这是一个无效的赋值。如果编译器允许您这样做,那么这样的场景会在运行时崩溃,因为SomeOtherTypeImplementingIEnumerable不能转换为List。但是,在使用ISomeData的这个帮助程序的上下文中,它无论如何都没有违反ISomeData接口,并且支持IEnumerable到.Data的任何类型的赋值都应该是有效的。因此,如果编译器允许,您的实现可能会破坏使用接口的完美代码。
这就是为什么你不能将.Data实现为派生类型的原因。您将.Data的实现更严格地限制在除List之外,而不是任何IEnumerable。因此,当界面显示“允许任何IEnumerable”时,您的具体实现将导致支持ISomeData的预先存在的代码在使用您的接口实现时突然中断。
当然,你并没有真正遇到只有一个getter的情况,但它会使事情变得复杂,以便在获取场景时使用它而不是其他。
我通常会选择Jake的解决方案或Alioto的解决方案,具体取决于我现在感觉有多挑剔。