在 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;
}
}
我正在为各种度量单位开发 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;
}
}