类的参数化族

本文关键字:参数 | 更新日期: 2023-09-27 17:57:59

我想构建一个

  • 仅进行计算(即不需要类的实例)
  • 所有这些都进行相同的计算,但参数不同,该参数被硬编码到每个类中

基于线程https://softwareengineering.stackexchange.com/q/325213/237600,我想出了这个:

public abstract class PrecisionSpace
{
    protected static double TOL; // This value is supposed to be
                                 // different in subclasses, so that...
    public static bool TolEquals(double left, double right) // ... this method, ...
    {
        return (Math.Abs(left - right) <= TOL);
    }
    public static bool TolSmaller(double left, double right) // ... this method and ...
    {
        return (left + TOL < right);
    }
    public static bool TolLarger(double left, double right) // ... this method behave
                                                            //     differently.
    {
        return TolSmaller(right, left);
    }
}

然后我想定义的子类

public class PerfectPrecision : PrecisionSpace
{
    (new) private static double 0; // doesn't matter if I put "new" in front or not
} 
public class MyPrecision: PrecisionSpace
{
    (new) private static double 1E-10;
}

并且能够使用像这样的语句

bool foo = MyPrecision.TolEquals(0, 1E-11); // supposed to be true

但它并没有像我想要的那样工作,因为PrecisionSpace的静态方法中没有使用子类中的TOL值。因此,无论我为T.TolEquals(...)放入哪个子类T,编译器总是使用在超类PrecisionSpace中定义的值(这是double的默认值,即0)。

简而言之,static字段不能被覆盖。那么我该怎么办呢?

我可以删除所有static修饰符,并在派生类的构造函数中设置所需的TOL值。但我总是需要类的实例。由于我并不真的需要实例,所以我可以使类成为singleton。但我必须确保它是线程安全的,这样。。。啊。

还有什么?

我可以用与PrecisionSpace相同的方式编写不相关的类,使用静态方法,用TOL代替01E-10,或者我为每个类选择的任何值。但这会产生很多冗余代码,而且由于类没有公共基类或接口,我无法正确定义泛型,比如:

public class MyVector<T> where T : (base class or interface of the Precision classes)

还有其他想法吗?也许和代表们有什么关系?

对于那些问"为什么"的人:请阅读我问题开头的链接。

类的参数化族

在.NET 6+中,这可以使用接口中的静态抽象成员来实现。它可以更进一步,使用通用数学来抽象数据类型(double):

interface IPrecisionSpace<TNumber> where TNumber : INumber<TNumber>
{
    static abstract TNumber TOL { get; }
}
static class PrecisionSpace<TNumber, TPrecision> where TNumber : INumber<TNumber> where TPrecision : IPrecisionSpace<TNumber>
{
    public static bool TolEquals(TNumber left, TNumber right)
    {
        return TNumber.Abs(left - right) <= TPrecision.TOL;
    }
    public static bool TolSmaller(TNumber left, TNumber right)
    {
        return left + TPrecision.TOL < right;
    }
    public static bool TolLarger(TNumber left, TNumber right)
    {
        return TolSmaller(right, left);
    }
}
class Vector<TNumber, TPrecision> where TNumber : INumber<TNumber> where TPrecision : IPrecisionSpace<TNumber>
{
    public Vector(TNumber number)
    {
        var isZero = PrecisionSpace<TNumber, TPrecision>.TolEquals(TNumber.Zero, number);
    }
}
class DoublePerfectPrecision : IPrecisionSpace<double>
{
    public static double TOL => 0;
}
class DoubleMyPrecision : IPrecisionSpace<double>
{
    public static double TOL => 1E-10;
}

用法:new Vector<double, DoublePerfectPrecision>(value: 1)


早期版本不支持对静态成员进行继承。如果你想要多态行为,你就必须创建实例。如果您的类被构建为不可变的,那么线程安全性就不应该是一个问题。

    public abstract class PrecisionSpace
    {
        private readonly double TOL;
        
        protected PrecisionSpace(double tol) { TOL = tol; }
    
        public bool TolEquals(double left, double right)
        {
            return (Math.Abs(left - right) <= TOL);
        }
    }
    
    public sealed class PerfectPrecision : PrecisionSpace
    {
        public PerfectPrecision() : base(0) { }
    }
    
    internal static class PrecisionSpaceCache<T> where T : PrecisionSpace, new()
    {
        public static readonly T Instance = new T();
    }
    
    public class MyVector<T> where T : PrecisionSpace, new()
    {
        public void Foo(double x, double y)
        {
            PrecisionSpaceCache<T>.Instance.TolEquals(x, y);
        }
    }

通过这种方式,您可以在其他类(例如Matrix)中使用PrecisionSpaceCache。此外,如果您计划创建大量向量、矩阵等类,我会考虑将它们转换为值类型(struct)。

另一种选择是只在Vector类的构造函数中传递精度。

在base.net框架中,您尝试做的事情的最佳近似是一系列Double、Int等类,它们公开相同的方法,但对不同的数据类型进行操作,并给出不同的结果。

在功能相同的情况下(例如ToString),框架使用一个共享的静态类来执行函数。这减少了编码的数量,但不使用继承等来减少端点的数量。

如果这一切都是为了语法方便,而其他考虑因素并不重要,那么您可以通过使用泛型类作为自己的参数来达到所需的效果,如下所示,允许您在静态方法中对其进行内部实例化。

public class Precision<T> where T : Precision<T>, new()
{
    protected virtual double TOL
    {
        get { throw new NotImplementedException(); }
    }
    public static bool TolEquals(double left, double right)
    {
        return (Math.Abs(left - right) <= new T().TOL);
    }
}

在派生类中,您可以覆盖虚拟方法。

public class PerfectPrecision : Precision<PerfectPrecision>
{
    protected override double TOL
    {
        get { return 0.00f; }
    }
}
public class MyPrecision : Precision<MyPrecision>
{
    protected override double TOL
    {
        get { return 0.01f; }
    }
}

这具有所需的效果,可以通过以下方式进行验证。

Console.WriteLine(PerfectPrecision.TolEquals(0.000f, 0.0001f));
Console.WriteLine(MyPrecision.TolEquals(0.000f, 0.0001f));

然而,这是有代价的。能够从继承中受益的类实例的生成对于调用者来说是不可见的,尽管如此,它们还是会发生。此外,这些类实际上并不是从同一个基类派生的,因为它们使用不同的类型参数。