【问题标题】:Awkward syntax of generic method with generic type constraint具有泛型类型约束的泛型方法的尴尬语法
【发布时间】:2015-05-02 03:26:42
【问题描述】:

我正在尝试创建一个度量单位类库。

到目前为止我所拥有的是:

public abstract class UnitBase
{
    protected double unitValue;
    protected readonly double toSiFactor;
    //more stuff goes here
}

//Meters, feet, miles etc will derive from this
public class LengthUnit : UnitBase
{
    //various methods and operator overloading

    //Among others there is To<> for conversions, not really needed
    //to understand the problem but relevant.
    public L To<L>() where L : LengthUnit, new()
    {
        L converted = new L();
        converted.FromSi(ToSi());  //ToSi() and FromSi() are in UnitBase, omitted for brevity
        return converted;
    }
}

//Seconds Hours etc will derive from this
public class TimeUnit : UnitBase
{
    //Add() Subtract methods and various operator overloading
}

到目前为止一切顺利。但现在我想创建复杂的单位类型,例如速度。所以这里是:

public class SpeedUnit<S, T> : UnitBase
    where S : LengthUnit, new()
    where T : TimeUnit, new()
{
    //=======================
    //HERE IS THE AWKWARDNESS
    //=======================
    public U To<U, S1, T1>()
        where U : SpeedUnit<S1, T1>, new()
        where S1 : LengthUnit, new()
        where T1 : TimeUnit, new()
    {
        U converted = new U();
        converted.FromSi(ToSi());
        return converted;
    }
}

public class Knots : SpeedUnit<NauticalMiles, Hours>
{
    //omitted code
}

public class FeetPerMinute : SpeedUnit<Feet, Minutes>
{
    //omitted code
}

所以这是我的问题:假设您有 Knots,并且您想将它们转换为 FeetPerMinute。理想的情况是:

Knots kts = new Knots(20);
FeetPerMinute = kts.To<FeetPerMinute>();

相反,我必须这样做:

FeetPerMinute = kts.To<FeetPerMinute, Feet, Minutes>();

这有点尴尬,而且当涉及到更复杂的类型(例如力)时,情况可能会变得更糟。 To() 将类似于:

Newtons n = someForce.To<Newtons, Kilograms, Meters, Seconds>()

如果你错误地使用了加速类型,甚至更糟:

Newtons n = someForce.To<Newtons, Kilograms, Acceleration<Meters, Seconds>, Meters, Seconds>()

不是很方便,特别是如果您考虑简单的话。 所以我的问题是:

  • 有什么方法可以使这项工作? (除了从SpeedUnit 中删除通用参数)
  • 为什么编译器臭名昭著的类型推断不能发现MetersSeconds 已经存在于MetersPerSecond 中?

【问题讨论】:

  • static From&lt;U, S1, T1&gt;(U value) 怎么样?
  • @OndrejTucny 啊不,我担心这行不通:From 的返回类型是什么? U?还是其他类型的U1?别忘了我想从Knots (U) 转换为FeetPerMinute (U1)。

标签: c# generics units-of-measurement


【解决方案1】:

简短的回答是,C# 不允许从其他泛型类型推断泛型类型,只能从参数推断。您也可以说泛型类型限制对此不够表达。

但是,我真的不明白为什么您甚至将不同的单元表示为不同的类?我个人的建议是为每个物理维度使用结构 - 而不是单位。所以,有一个这样的结构:

public struct Length {
    public static Length FromMeters(double meters) {
        // ...
    }

    public double InMiles() { ... }

    // operator overloads
}

这样您就不会遇到任何泛型问题,甚至不会对运行时产生影响,但仍然有编译时支持。

假设您有结构LengthTimeSpeed,那么您可以轻松地重载运算符,使您可以将Length 除以Time 并得到Speed 作为结果。然后,您可以通过(速度)方法以您喜欢的任何单位查询速度值,例如InMilesPerSecond 返回一个双精度值。

编辑: 我想您最初想法中的真正问题是错误抽象的问题。特别是,速度不是(在同一意义上)长度除以时间,尽管您可以这样表达。这是细微的差别。例如,您可以表示 1W = 1Nm 或 1W = 1VA。因此,您不应该对 PowerType、Length> 建模,因为这不是幂的恒等式,而只是一种计算方式。

【讨论】:

  • 我前段时间在 uservoice 上为此创建了一个请求:visualstudio.uservoice.com/forums/121579-visual-studio/…
  • 但是,我真的不明白为什么你甚至将不同的单位表示为不同的类?——为了防止未来的火星着陆器因混淆英里表示为@而崩溃987654330@ 公里表示为完全相同的double? :-)
  • @OndrejTucny 有很多更好的方法可以拯救火星探险者,尤其是我刚刚提出的解决方案,除了我的版本没有运行时影响,因为内存是火星探险者的宝贵且有限的资源。使用结构,您仍然可以重载运算符,ToMiles 只是当您最终将 Length 构造转换为 double
  • 从嵌入式系统的性能角度来看,我同意。但是,对于通用用途,OP 探索的方法似乎非常合法(我不在乎他在这个阶段是使用类还是结构)。
  • @OndrejTucny 在 OP 的方法中,您不能使用结构,因为它们不支持继承是有充分理由的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多