C# 中的度量单位 - 几乎 [英] Units of measure in C# - almost

查看:11
本文介绍了C# 中的度量单位 - 几乎的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

受到 F# 中的度量单位 的启发,尽管有断言(此处)你做不到在 C# 中,前几天我有一个想法,我一直在玩.

Inspired by Units of Measure in F#, and despite asserting (here) that you couldn't do it in C#, I had an idea the other day which I've been playing around with.

namespace UnitsOfMeasure
{
    public interface IUnit { }
    public static class Length
    {
        public interface ILength : IUnit { }
        public class m : ILength { }
        public class mm : ILength { }
        public class ft : ILength { }
    }
    public class Mass
    {
        public interface IMass : IUnit { }
        public class kg : IMass { }
        public class g : IMass { }
        public class lb : IMass { }
    }

    public class UnitDouble<T> where T : IUnit
    {
        public readonly double Value;
        public UnitDouble(double value)
        {
            Value = value;
        }
        public static UnitDouble<T> operator +(UnitDouble<T> first, UnitDouble<T> second)
        {
            return new UnitDouble<T>(first.Value + second.Value);
        }
        //TODO: minus operator/equality
    }
}

示例用法:

var a = new UnitDouble<Length.m>(3.1);
var b = new UnitDouble<Length.m>(4.9);
var d = new UnitDouble<Mass.kg>(3.4);
Console.WriteLine((a + b).Value);
//Console.WriteLine((a + c).Value); <-- Compiler says no

下一步是尝试实现转换(代码段):

The next step is trying to implement conversions (snippet):

public interface IUnit { double toBase { get; } }
public static class Length
{
    public interface ILength : IUnit { }
    public class m : ILength { public double toBase { get { return 1.0;} } }
    public class mm : ILength { public double toBase { get { return 1000.0; } } }
    public class ft : ILength { public double toBase { get { return 0.3048; } } }
    public static UnitDouble<R> Convert<T, R>(UnitDouble<T> input) where T : ILength, new() where R : ILength, new()
    {
        double mult = (new T() as IUnit).toBase;
        double div = (new R() as IUnit).toBase;
        return new UnitDouble<R>(input.Value * mult / div);
    }
}

(我本来希望避免使用静态实例化对象,但众所周知,您 不能在接口中声明静态方法)然后你可以这样做:

(I would have liked to avoid instantiating objects by using static, but as we all know you can't declare a static method in an interface) You can then do this:

var e = Length.Convert<Length.mm, Length.m>(c);
var f = Length.Convert<Length.mm, Mass.kg>(d); <-- but not this

显然,与 F# 度量单位相比,这其中存在一个巨大的漏洞(我会让你解决).

Obviously, there is a gaping hole in this, compared to F# Units of measure (I'll let you work it out).

哦,问题是:你怎么看这个?值得使用吗?其他人已经做得更好了吗?

Oh, the question is: what do you think of this? Is it worth using? Has someone else already done better?

更新对于对此主题领域感兴趣的人,这里是 1997 年一篇论文的链接,该论文讨论了一种不同的解决方案(不是专门针对 C#)

UPDATE for people interested in this subject area, here is a link to a paper from 1997 discussing a different kind of solution (not specifically for C#)

推荐答案

您缺少维度分析.例如(根据您链接的答案),在 F# 中,您可以这样做:

You are missing dimensional analysis. For example (from the answer you linked to), in F# you can do this:

let g = 9.8<m/s^2>

它会生成一个新的加速度单位,从米和秒派生出来(你实际上可以在 C++ 中使用模板做同样的事情).

and it will generate a new unit of acceleration, derived from meters and seconds (you can actually do the same thing in C++ using templates).

在 C# 中,可以在运行时进行维度分析,但它会增加开销并且不会为您提供编译时检查的好处.据我所知,没有办法在 C# 中完成完整的编译时单元.

In C#, it is possible to do dimensional analysis at runtime, but it adds overhead and doesn't give you the benefit of compile-time checking. As far as I know there's no way to do full compile-time units in C#.

是否值得做当然取决于应用,但对于许多科学应用来说,这绝对是一个好主意.我不知道任何现有的 .NET 库,但它们可能存在.

Whether it's worth doing depends on the application of course, but for many scientific applications, it's definitely a good idea. I don't know of any existing libraries for .NET, but they probably exist.

如果您对如何在运行时执行此操作感兴趣,其想法是每个值都有一个标量值和表示每个基本单位幂的整数.

If you are interested in how to do it at runtime, the idea is that each value has a scalar value and integers representing the power of each basic unit.

class Unit
{
    double scalar;
    int kg;
    int m;
    int s;
    // ... for each basic unit

    public Unit(double scalar, int kg, int m, int s)
    {
       this.scalar = scalar;
       this.kg = kg;
       this.m = m;
       this.s = s;
       ...
    }

    // For addition/subtraction, exponents must match
    public static Unit operator +(Unit first, Unit second)
    {
        if (UnitsAreCompatible(first, second))
        {
            return new Unit(
                first.scalar + second.scalar,
                first.kg,
                first.m,
                first.s,
                ...
            );
        }
        else
        {
            throw new Exception("Units must match for addition");
        }
    }

    // For multiplication/division, add/subtract the exponents
    public static Unit operator *(Unit first, Unit second)
    {
        return new Unit(
            first.scalar * second.scalar,
            first.kg + second.kg,
            first.m + second.m,
            first.s + second.s,
            ...
        );
    }

    public static bool UnitsAreCompatible(Unit first, Unit second)
    {
        return
            first.kg == second.kg &&
            first.m == second.m &&
            first.s == second.s
            ...;
    }
}

如果您不允许用户更改单位的值(无论如何都是个好主意),您可以为常用单位添加子类:

If you don't allow the user to change the value of the units (a good idea anyways), you could add subclasses for common units:

class Speed : Unit
{
    public Speed(double x) : base(x, 0, 1, -1, ...); // m/s => m^1 * s^-1
    {
    }
}

class Acceleration : Unit
{
    public Acceleration(double x) : base(x, 0, 1, -2, ...); // m/s^2 => m^1 * s^-2
    {
    }
}

您还可以在派生类型上定义更具体的运算符,以避免检查常见类型上的兼容单位.

You could also define more specific operators on the derived types to avoid checking for compatible units on common types.

这篇关于C# 中的度量单位 - 几乎的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆