类的参数化族
本文关键字:参数 | 更新日期: 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
代替0
或1E-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));
然而,这是有代价的。能够从继承中受益的类实例的生成对于调用者来说是不可见的,尽管如此,它们还是会发生。此外,这些类实际上并不是从同一个基类派生的,因为它们使用不同的类型参数。