所以我有以下场景:
我正在编写一个程序,它接受各种类型(字符串、字节等(数组中的内容,将内容解析为数据结构,然后将结构化数据存储在某个地方。由于解析方法可能会有很大的不同,这取决于接收到的数据类型,所以我想定义单独的文件解析类,但要定义它们都应该实现的方法和属性。我想使用一个接口来实现这一点,但我不知道如何告诉接口,例如,它的ParseFile方法将接受某种类型的数组作为输入参数。如果我在一个界面中写下如下内容:
void ParseFile(Array[] fileContents);
然后尝试在ByteParser类中实现该方法,如下所示:
public void ParseFile(Byte[] fileContents){
//Do whatever
}
我得到一个编译器错误,我的实现类没有实现ParseFile方法。我认为这是因为编译器默认情况下不进行强制转换,即使ByteArray是Array的派生,它也不是Array类型。
有没有什么方法可以做我在这里试图做的事情,强制接口接受任何类型的数组,并允许我的实现类从那里处理它?
您当前的想法(使用Array
(而不是Array[]
(和Byte[]
(……信息不灵通,建议不周。
例如。NET要求接口的实现与接口定义完全匹配,因此如果您有方法void ParseFile(Array fileContents)
,则实现必须也有void ParseFile(Array fileContents)
,而不能有ParseFile(Byte[] fileContents)
。以下是您不能的原因:
假设这实际上是编译的:
interface IFileBuilder {
void ParseFile(Array fileContents);
}
class OctetFileBuilder : IFileBuilder {
public void ParseFile(Byte[] fileContents) {
// ...
}
}
然后运行这个:
void Main() {
Byte[] byteArray = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
String[] strArray = new String[] { "foo", "bar", "baz" };
OctetFileBuilder ofb = new OctetFileBuilder();
ofb.ParseFile( byteArray ); // OK, so far so good.
ofb.ParseFile( strArray ); // Compiler rejects this because `String[]` isn't `Byte[]`. That's also OK.
IFileBuilder fb = ofb;
fb.ParseFile( byteArray ); // This would be okay because a `Byte[]` can be the argument for an `Array` parameter.
fb.ParseFile( strArray ); // But what happens here?
}
最后一行是问题:fb.ParseFile( strArray )
。
实现需要Byte[]
,但它被传递了一个String[]
。
我想你会期待的。NET引发运行时错误,或者它会神奇地知道如何将String[]
转换为Byte[]
(不,不会(。相反,整个程序根本无法编译。
现在,在现实中,为了构建,您的实现必须是这样的
class OctetFileBuilder : IFileBuilder {
public void ParseFile(Array fileContents) {
Byte[] byteArray = (Byte[])fileContents; // Runtime cast.
}
}
因此,现在您的代码将编译并开始运行,但它在完成之前就会崩溃,因为当ParseFile
被赋予String[]
时,它将导致InvalidCastException
,因为您无法有意义地直接从String[]
强制转换为Byte[]
。
解决方案:协变泛型
这就是泛型类型(以及逆变和协方差(的全部内容,也是为什么在设计多态接口时,您需要仔细考虑程序内数据移动的方向(一般来说,"正向"/"输入"数据可以"收缩","输出"数据可以是"增长"/"扩展"/"延伸"-但不能反过来。这就是in
和out
通用类型修饰符的作用
所以我会这样做:(免责声明:我不知道你的接口实际上是用来做什么的,也不知道为什么你想要ParseFile
方法参数使用Byte[]
以外的任何东西…(
interface IFileBuilder<in TInput>
{
void ParseFile( IReadOnlyList<TInput> fileContents )
}
class OctetFileBuilder : IFileBuilder<Byte> {
public void ParseFile(Byte[] fileContents) {
// ...
}
}
(请注意,在这种特殊情况下使用反方差/协方差修饰符(in TInput
部分(是毫无意义的,因为中的基元和基类型。NET(Byte
、Int32
、String
等(要么是没有继承的结构,要么是密封类型(。
用法:
void Main() {
Byte[] byteArray = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
String[] strArray = new String[] { "foo", "bar", "baz" };
OctetFileBuilder ofb = new OctetFileBuilder();
ofb.ParseFile( byteArray ); // OK, so far so good.
ofb.ParseFile( strArray ); // Compiler rejects this because `String[]` isn't `Byte[]`. That's also OK.
IFileBuilder<Byte> fb = ofb;
fb.ParseFile( byteArray ); // OK
fb.ParseFile( strArray ); // Compiler error: `String[]` is not `IReadOnlyList<Byte>`.
}
因此,假设的运行时错误现在是一个有保证的编译时错误。
编译时错误比运行时错误要好得多,这种方法也意味着您可以推断应用程序中数据流的方向。