代码之家  ›  专栏  ›  技术社区  ›  Daniel A.A. Pelsmaeker

C:支持重写的类型特定/静态方法

  •  1
  • Daniel A.A. Pelsmaeker  · 技术社区  · 14 年前

    在C中,不可能重写静态方法。不过,我有一些特定于类型的方法(而不是通常的实例方法),我想让用户能够重写这些方法,此外,还可以从我自己的代码调用那些(重写的)方法,而不知道用户所做的“重写”。如果方法是静态的,我必须提前知道用户类的类型名,这不是我想要的。

    如何实现类型特定的方法,如 Read , Write GetLength 允许覆盖?

    背景

    有一个摘要 Row 其派生类型/类表示 行的类型 其实例在一个非常简单的表中表示实际的行及其字段/数据。每种类型的行都有一个固定的长度,而表只是一个文件中的首尾行。这个 类需要三种方法: 在给定偏移量的流上执行其明显功能的方法,以及 获取长度 方法,返回行类型的固定长度。

    现在用户可以扩展 类并为提供实现 , 获取长度 为他或她的具体 行类型 ,以及要在其特定的 行实例 . 例如, SomeUserRow 类可以有一个32位整数字段和一个单字节字段,固定长度为5个字节,以及相应的读写方法实现。

    方法

    一个明显的工厂方法,与类型相关,因此我将在类本身中定义它。我会成功的 static ,但不能重写。那我就能成功了 protected abstract 并创建一个静态泛型 Read<T> 方法调用它,如中所建议的 this post 。但我还需要能够从代码中调用这个方法,而不知道用户实现类的类型。我不能打电话 Row.Read<UserType>() 因为我还不知道用户的类型。

    一个可能的实例方法,因为大多数人都希望编写一个现有的 去小溪边。但是有 静态,看起来很奇怪 实例方法。

    获取长度

    不是工厂方法,但仍与类型相关。同样,我会使它成为静态的,但这样可以防止重写。我可以选择使它成为一个实例方法, 可以 被重写,但在面向对象的环境中这样做是错误的:创建一个实例只是为了得到一个不依赖于该实例的值( int length = new T().GetLength() )而是它的类型。

    我也在考虑将三个方法从类中移到一个单独的类中,但这仍然不能解决重写问题。或者拥有一个类,该类保存一个指向正确方法的委托列表或字典。它不允许 真实的 重写(我不认为替换委托数组中的方法指针 真的 重写),从开发人员的角度来看,它将特定于类型的方法从我认为不好的类型移开:当您只想更改类型时,有两个或多个地方可以更改。

    通过反射可以对一个类型调用正确的静态方法,但是当我读了很多行时,发现它太慢了。

    4 回复  |  直到 14 年前
        1
  •  2
  •   driis    14 年前

    我觉得你应该 IRowReader 接口,带有 Read 方法。然后可以有多个实现,返回 Row (或子类,如果需要,可以重写 Write GetLength )

    换句话说,您需要一个工厂类型,其中包含您需要重写的方法。如您所述,不能重写C中的静态方法。

    当然,问题是,如何获得正确的iRowreder实例?这取决于应用程序,但您可以让客户机在启动时或应用程序中的其他定义良好的点对其进行初始化;或者您可以使用 Dependency Injection 注入和配置。

        2
  •  0
  •   Timwi    14 年前

    我也碰到了这个问题,没有一个干净的解决方案,真令人沮丧。这里有一个我用过的解决方法,也许你会喜欢。

    假设您的层次结构 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(...));
    
        3
  •  0
  •   Derar    14 年前

    我想抽象基类(rowbase)和工厂类可以解决这个问题。您可以使用配置文件或其他东西来标识您感兴趣的派生类的类型。基本上,您使用的是策略模式。

        4
  •  0
  •   Timwi    14 年前

    一种可能的策略是使用静态方法,您将通过反射发现这种方法。幸运的是,您只需要编写一次反射代码。

    假设您的层次结构是静态的 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(...));
    
    推荐文章