在 C# 中使用 Base 类 中的泛型:如何确保 base class return 中的方法是派生的 class 的类型?

Using Generics in Base Classes in C#: How to ensure methods in the base class return the derived class's type?

我正在为各种度量单位开发 class 库。我试图尽可能避免重复代码,这应该非常简单,因为从数学上讲,无论是将两个长度相加还是将两个速度相加都没有关系——您只需将它们转换为相同的单位,然后添加。我 认为 我可以使用基础 class 和泛型很容易地做到这一点...

// A measurement consists of a Value (double) and
// a Unit (an Enum for the various unit types).

public class Measurement<TUnit>
    where TUnit : struct
{
    protected Measurement(double value, TUnit unit)
    {
        _value = value;
        _unit = unit;
    }

    protected double _value;
    protected TUnit _unit;

    public double Value => _value;
    public TUnit Unit => _unit;

    ...
    // Conversion Methods - these will get overridden in the derived classes.
    protected virtual double GetValueAs(TUnit unit) => throw new NotImplementedException();
    ...

    // Operator overloads
    public static Measurement<TUnit> operator +(Measurement<TUnit> left,
                                                Measurement<TUnit> right)
    {
        return new Measurement<TUnit>(left.Value + right.GetValueAs(left.Unit), left.Unit);
    }
}

而这个 class 是为每个单位派生的,如下所示:

public sealed class Length : Measurement<LengthUnit>
{
    // Using a private constructor and public static factory methods
    private Length(double value, LengthUnit unit)
        : base(value, unit) { }

    ...
}

我的问题是,每当我尝试使用 return Measurement<TUnit> 的任何基本 class 类型时,这些方法显然是 return Measurement<TUnit> 对象.

Length length1 = Length.FromMeters(1); // length1 is 1 meter

Length length2 = Length.FromMeters(2); // length2 is 2 meters

Length length3 = length1 + length2;    // Error CS0266: Cannot implicitly convert type
                                       // 'Measurement<LengthUnit>' to 'Length'.
                                       // An explicit conversion exists (are you missing a cast?)

var varlength3 = length1 + length2;    // This works, but varlength3 is type Measurement<LengthUnit>
                                       // so I can't use any methods in the Length class.

我错过了什么?甚至可以做我想做的事吗?我是否必须硬着头皮将相同的代码复制到每个单元中class?

检查您的运算符过载:

// Operator overloads
public static Measurement<TUnit> operator +(Measurement<TUnit> left,
                                            Measurement<TUnit> right)
{
    return new Measurement<TUnit>(left.Value + right.GetValueAs(left.Unit), left.Unit);
}

您正在返回一个 Measurement<TUnit> 对象。所以编译器不知道 Measurement<TUnit> class 是否总是一个长度对象,需要你做像

这样的转换
Length length3 = (Length)(length1 + length2);

您当然可以避免这种情况,方法是创建一个用于添加 Length 的新运算符,该运算符会添加 Length 个对象。

简答:当你需要从base操作派生的classes时,可以通过实现Curiously Recurring Template Pattern来完成,它最初来自C++,但也可以应用于C#。

长答案: 在你的例子中,base Measurement class 应该有一个额外的通用参数,它是派生的 class 类型。 唯一的限制是,如果派生的 classes 具有带参数的构造函数,则不能在基础 classes 中使用 new 创建派生实例。在下面的示例中,我使用 MemberwiseClone 可能会解决问题。

public class Measurement<TUnit, TMeasurement>
    where TUnit : struct
    where TMeasurement: Measurement<TUnit, TMeasurement>
{
    private readonly double _value;
    protected Measurement(double value, TUnit unit)
    {
        _value = value;
        Unit = unit;
    }
    protected double Value { get; set; }
    public TUnit Unit { get; protected set; }

    // Conversion Methods - these will get overridden in the derived classes.
    protected virtual double GetValueAs(TUnit unit) => throw new NotImplementedException();

    // Operator overloads
    public static TMeasurement operator +(Measurement<TUnit, TMeasurement> left, Measurement<TUnit, TMeasurement> right)
    {
        //we cannot create new instance of derived class, TMeasurement, which is limitation of generics in C#, so need some workaround there
        //Some kind of clone might be solution for that
        var leftClone = (TMeasurement)left.MemberwiseClone();  
        var resultValue =  leftClone.Value + right.GetValueAs(left.Unit);
        leftClone.Unit = left.Unit;
        leftClone.Value = resultValue;
        return leftClone;
    }
}

public struct LengthUnit
{

}

public sealed class LengthMeasurement : Measurement<LengthUnit, LengthMeasurement>
{
    private LengthMeasurement(double value, LengthUnit unit): base(value, unit)
    {

    }

    public static LengthMeasurement FromMeters(double meters) => throw new NotImplementedException();
}

class Program
{
    static void Main(string[] args)
    {
        var length1 = LengthMeasurement.FromMeters(5);
        var length2 = LengthMeasurement.FromMeters(10);
        LengthMeasurement length3 = length1 + length2;
    }
}