在C#中,无法覆盖静态方法。不过我有一些特定于类型的方法(而不是通常的实例方法),我想让用户能够覆盖这些方法,此外,还能够从我自己的代码调用那些(重写的)方法,而不需要知道用户完成的“覆盖”。如果方法是静态的,我必须事先知道用户类的类型名称,这不是我想要的。
如何实施类型特定的方法,例如Read
,Write
和GetLength
并允许覆盖?
有一个抽象的Row
类,其派生类型/类表示类型的行,其实例表示实际行及其字段/数据在一个非常简单的表中。每种类型的行都有一个固定长度,表格只是文件中行的一对一行。 Row
类需要三种方法:Read
和Write
方法,它们在给定偏移的流上执行它们的明显函数,以及GetLength
方法,它返回固定长度的行的类型。
现在,用户可以扩展我的Row
课程,并针对他或她的特定行类型提供Read
,Write
和GetLength
的实施,以及要在其特定行实例中使用的字段和属性。例如,SomeUserRow
类可以具有32位整数字段和单字节字段,固定长度为5个字节以及相应的读写方法实现。
一个明显的工厂方法,与类型有关,因此我会在类本身中定义它。我将它static
,但它不能被覆盖。然后我可以将其设为protected abstract
并创建一个静态通用Read<T>
方法来调用它,如this post中所述。但我还需要能够在不知道用户实现类的类型的情况下从我的代码中调用此方法。我不能只调用Row.Read<UserType>()
,因为我还不知道用户的类型。
一种可能的实例方法,因为大多数人都希望将现有的Row
写入流中。但是Read
静态,使Write
成为实例方法似乎很奇怪。
不是工厂方法,但仍与类型相关。再一次,我会让它静止,但这可以防止覆盖。我可以选择使它成为一个实例方法,可以被覆盖,但在面向对象的环境中这样做是错误的:创建一个实例只是为了得到一个不依赖于实例的值( int length = new T().GetLength()
)而是它的类型。
我一直在考虑将这三个方法从类中移出到一个单独的类中,但这仍然没有解决重写问题。或者有一个类,它保持委托的列表或字典指向正确的方法。它不允许真正的覆盖(替换委托数组中的方法指针不是我认为的 true 覆盖)并且它将类型特定的方法移离从开发人员的角度来看,我认为不好的类型:当你只想改变类型时,有两个或更多的地方要改变。
通过反射,可以在类型上调用正确的静态方法,但是当我读取很多行时,我发现它太慢了。
答案 0 :(得分:2)
我认为您应该使用IRowReader
方法创建Read
接口。然后你可以有多个这样的实现,它返回Row
(如果需要,可以返回一个子类,然后可以覆盖Write
或GetLength
)。
换句话说,您需要一个工厂类型,其中包含您需要覆盖的方法。如您所述,您无法在C#中覆盖静态方法。
当然问题是,你如何获得正确的IRowReader实例?这取决于应用程序,但您可以让您的客户端在启动时或应用程序中的其他明确定义的点初始化它;或者你可以使用Dependency Injection来注入和配置它。
答案 1 :(得分:0)
我也遇到过这个问题,并且没有“干净”的解决方案令人沮丧。这是我使用过的解决方法,也许你会喜欢它。
假设您有一个以Row
为基类的层次结构。
public class Row { }
public class DerivedRow : Row { }
创建与此平行相似的另一个层次结构,但将其基于名为Factory
的抽象基类。
public abstract class Factory {
public abstract Row Read(Stream stream);
public abstract int GetLength();
}
public class RowFactory : Factory {
public override Row Read(Stream stream) { /* read a base row */ }
public override int GetLength() { return /* length of base row */ }
}
public class DerivedRowFactory : Factory {
public override Row Read(Stream stream) { /* read a derived row */ }
public override int GetLength() { return /* length of derived row */ }
}
现在,您可以编写将工厂作为类型参数的方法,例如
public IEnumerable<Row> ReadRows<TRowFactory>(Stream stream)
where TRowFactory : Factory, new()
{
var factory = new TRowFactory();
var numRows = stream.Length / factory.GetLength();
for (long i = 0; i < numRows; i++)
yield return factory.Read(stream);
}
// Example call...
var rowsInFile = ReadRows<DerivedRowFactory>(File.Open(...));
答案 2 :(得分:0)
我猜抽象基类(RowBase)和工厂类可以解决这个问题。您可以使用配置文件或其他内容来识别您感兴趣的派生类类型。基本上你在这里使用战略模式。
答案 3 :(得分:0)
一种可能的策略是使用您将通过Reflection发现的静态方法。幸运的是,您只需编写一次Reflection代码。
假设您有一个静态Read
和GetLength
方法的层次结构:
public class Row {
public static Row Read(Stream stream) { /* read a base row */ }
public static int GetLength() { return /* length of base row */ }
}
public class DerivedRow : Row {
public static DerivedRow Read(Stream stream) { /* read a derived row */ }
public static int GetLength() { return /* length of derived row */ }
}
现在你可以编写一个实用程序函数来读取任何类型的行:
public static class RowUtils
{
public static Row Read<T>(Stream stream) where T : Row
{
var method = typeof(T).GetMethod("Read",
BindingFlags.Static | BindingFlags.Public);
if (method == null || !typeof(Row).IsAssignableFrom(method.ReturnType))
throw new InvalidOperationException(string.Format(
"Static Read method on type “{0}” does not exist or " +
"has the wrong return type.", typeof(T).FullName));
return (Row) method.Invoke(null, new object[] { stream });
}
}
GetLength()
当然也是如此。然后,您可以编写将行类型作为类型参数的方法,例如
public IEnumerable<Row> ReadRows<TRow>(Stream stream)
where TRow : Row
{
var numRows = stream.Length / RowUtils.GetLength<TRow>();
for (long i = 0; i < numRows; i++)
yield return RowUtils.Read<TRow>(stream);
}
// Example call...
var rowsInFile = ReadRows<DerivedRow>(File.Open(...));