In C# it is not possible to override a static method. Nevertheless I have some type specific methods (instead of the usual instance methods) and I want to give the user the ability to override those methods, and in addition, have the ability to call those (overridden) methods from my own code without knowing about the 'overriding' done by the user. Would the method be static, I would have to know the type name of the user's class in advance, which is not what I want.
How to implement type specific methods like Read
, Write
and GetLength
and allow overriding?
Background
There is an abstract Row
class whose derived types/classes represents a type of row and whose instance represents the actual row and its fields/data in a very simple table. Each type of row has a fixed length and the table is just a head-to-tail run of rows in a file. The Row
class needs three methods: Read
and Write
methods which perform their obvious function on a stream given an offset, and a GetLength
method which returns the fixed length of the type of row.
Now the user may extend my Row
class and provide implementations for Read
, Write
and GetLength
for his or her specific row type, and fields and properties to be used in his or h开发者_如何学Goer specific row instance. For example, the SomeUserRow
class may have a 32-bit integer field and a single byte field, a fixed length of 5 bytes and corresponding read and write method implementation.
The methods
Read
An obvious factory method, related to the type and therefore I would define it in the class itself. I'd make it static
, but then it cannot be overridden. Then I could make it protected abstract
and create a static generic Read<T>
method to call it, as suggested in this post. But I also need to be able to call this method from my code without knowing the type of the user implemented class. I can't just call Row.Read<UserType>()
because I don't know about the user's types yet.
Write
A possible instance method, because most people want to write an existing Row
to the stream. But having Read
static, it seems weird to make Write
an instance method.
GetLength
Not a factory method, but still related to the type. Again, I would make it static but this prevents overriding. I can choose to make it an instance method, which can be overridden but it feels wrong to do that in an Object Oriented environment: creating an instance just to get a value which does not depend on the instance (int length = new T().GetLength()
) but rather on its type.
I have also been thinking about moving the three methods out of the classes into a separate class, but that still does not address the overriding. Or to have a class which keeps a list or dictionary of delegates pointing to the correct methods. It does not allow for real overriding (replacing a method pointer in a delegate array is not what I'd consider true overriding) and it moves the type specific methods away from the type which I think is not good from a developers point of view: having two or more places to change when you just want to change the type.
Via reflection it is possible to call the correct static method on a type, but as I was reading many rows, I found it to be too slow.
I think you should make a IRowReader
interface, with a Read
method. Then you can have multiple implementations of this, which returns Row
(or a subclass if needed, which can then override Write
or GetLength
).
In other words, you need a factory type, which contain the method, that you need to override. As you stated, you cannot override a static method in C#.
Then of course the question is, how do you obtain the correct IRowReader instance ? That depends on the application, but you could have your client initialize it at startup or some other well-defined point in the application; or you could use Dependency Injection to have it injected and configurable.
I have run into this problem too, and it is frustrating that there is no “clean” solution. Here is a workaround that I used, maybe you will like it.
Let’s assume you have a hierarchy with Row
as the base class.
public class Row { }
public class DerivedRow : Row { }
Create another hierarchy that parallels this one, but base it on an abstract base class called 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 */ }
}
Now you can write methods that take a factory as a type parameter, e.g.
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(...));
I guess abstract base class (RowBase)and a factory classes would solve this. You can identify which type of derived class you are interested in by using a configuration file or something. Basically you are using the Strategy Pattern here.
One possible strategy is to use a static method that you will discover via Reflection. You only need to write the Reflection code once, fortunately.
Let’s assume you have a hierarchy with static Read
and GetLength
methods:
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 */ }
}
Now you can write a utility function that will read a row of any type:
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 });
}
}
The same of course for GetLength()
. Then you can write methods that take a row type as a type parameter, e.g.
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(...));
精彩评论