C# 中的度量单位 - 几乎

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/348853/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 00:04:17  来源:igfitidea点击:

Units of measure in C# - almost

c#f#units-of-measurement

提问by Benjol

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.

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

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
    }
}

Example usage:

用法示例:

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

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

显然,与 F# 度量单位相比,这里面有一个很大的漏洞(我会让你解决这个问题)。

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

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

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

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

回答by Mark Cidade

Using separate classes for different units of the same measure (e.g., cm, mm, and ft for Length) seems kind of weird. Based on the .NET Framework's DateTime and TimeSpan classes, I would expect something like this:

为相同度量的不同单位(例如,cm、mm 和 ft 表示长度)使用不同的类似乎有点奇怪。基于 .NET Framework 的 DateTime 和 TimeSpan 类,我希望是这样的:

Length  length       = Length.FromMillimeters(n1);
decimal lengthInFeet = length.Feet;
Length  length2      = length.AddFeet(n2);
Length  length3      = length + Length.FromMeters(n3);

回答by Ray Tayek

there is jscience: http://jscience.org/, and here is a groovy dsl for units: http://groovy.dzone.com/news/domain-specific-language-unit-. iirc, c# has closures, so you should be able to cobble something up.

有 jscience:http://jscience.org/ ,这里有一个用于单位的 groovy dsl:http: //groovy.dzone.com/news/domain-specific-language-unit- 。iirc,c# 有闭包,所以你应该能够拼凑出一些东西。

回答by Matthew Crumley

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

您缺少维度分析。例如(根据您链接的答案),在 F# 中,您可以执行以下操作:

let g = 9.8<m/s^2>

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++ 中使用模板做同样的事情)。

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#.

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

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.

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

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.

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

回答by Jonathan C Dickinson

Why not use CodeDom to generate all possible permutations of the units automatically? I know it's not the best - but I will definitely work!

为什么不使用 CodeDom 自动生成所有可能的单位排列?我知道这不是最好的 - 但我一定会工作!

回答by justin.m.chase

See Boo Ometa (which will be available for Boo 1.0): Boo Ometa and Extensible Parsing

请参阅 Boo Ometa(可用于 Boo 1.0): Boo Ometa 和可扩展解析

回答by Drew Noakes

You could add extension methods on numeric types to generate measures. It'd feel a bit DSL-like:

您可以在数字类型上添加扩展方法以生成度量。感觉有点像 DSL:

var mass = 1.Kilogram();
var length = (1.2).Kilometres();

It's not really .NET convention and might not be the most discoverable feature, so perhaps you'd add them in a devoted namespace for people who like them, as well as offering more conventional construction methods.

它并不是真正的 .NET 约定,也可能不是最容易发现的功能,因此也许您会将它们添加到专门的命名空间中,供喜欢它们的人使用,并提供更传统的构造方法。

回答by ja72

Thanks for the idea. I have implemented units in C# many different ways there always seems to be a catch. Now I can try one more time using the ideas discussed above. My goal is to be able to define new units based on existing ones like

谢谢你的想法。我已经在 C# 中实现了许多不同的单元,但似乎总是有问题。现在我可以使用上面讨论的想法再试一次。我的目标是能够根据现有单位定义新单位,例如

Unit lbf = 4.44822162*N;
Unit fps = feet/sec;
Unit hp = 550*lbf*fps

and for the program to figure out the proper dimensions, scaling and symbol to use. In the end I need to build a basic algebra system that can convert things like (m/s)*(m*s)=m^2and try to express the result based on existing units defined.

并让程序找出要使用的正确尺寸、比例和符号。最后,我需要构建一个基本的代数系统,它可以转换类似的东西,(m/s)*(m*s)=m^2并尝试根据定义的现有单位来表达结果。

Also a requirement must be to be able to serialize the units in a way that new units do not need to be coded, but just declared in a XML file like this:

此外,还必须能够以不需要对新单元进行编码的方式序列化单元,而只需在 XML 文件中声明如下:

<DefinedUnits>
  <DirectUnits>
<!-- Base Units -->
<DirectUnit Symbol="kg"  Scale="1" Dims="(1,0,0,0,0)" />
<DirectUnit Symbol="m"   Scale="1" Dims="(0,1,0,0,0)" />
<DirectUnit Symbol="s"   Scale="1" Dims="(0,0,1,0,0)" />
...
<!-- Derived Units -->
<DirectUnit Symbol="N"   Scale="1" Dims="(1,1,-2,0,0)" />
<DirectUnit Symbol="R"   Scale="1.8" Dims="(0,0,0,0,1)" />
...
  </DirectUnits>
  <IndirectUnits>
<!-- Composite Units -->
<IndirectUnit Symbol="m/s"  Scale="1"     Lhs="m" Op="Divide" Rhs="s"/>
<IndirectUnit Symbol="km/h" Scale="1"     Lhs="km" Op="Divide" Rhs="hr"/>
...
<IndirectUnit Symbol="hp"   Scale="550.0" Lhs="lbf" Op="Multiply" Rhs="fps"/>
  </IndirectUnits>
</DefinedUnits>

回答by Scott

Here's my concern with creating units in C#/VB. Please correct me if you think I'm wrong. Most implementations I've read about seem to involve creating a structure that pieces together a value (int or double) with a unit. Then you try to define basic functions (+-*/,etc) for these structures that take into account unit conversions and consistency.

这是我对在 C#/VB 中创建单元的关注。如果您认为我错了,请纠正我。我读过的大多数实现似乎都涉及创建一个将值(int 或 double)与单位拼凑在一起的结构。然后,您尝试为这些考虑了单位转换和一致性的结构定义基本函数(+-*/ 等)。

I find the idea very attractive, but every time I balk at what a huge step for a project this appears to be. It looks like an all-or-nothing deal. You probably wouldn't just change a few numbers into units; the whole point is that all data inside a project is appropriately labeled with a unit to avoid any ambiguity. This means saying goodbye to using ordinary doubles and ints, every variable is now defined as a "Unit" or "Length" or "Meters", etc. Do people really do this on a large scale? So even if you have a large array, every element should be marked with a unit. This will obviously have both size and performance ramifications.

我发现这个想法非常有吸引力,但每次我都对这似乎是一个项目迈出的巨大一步感到犹豫。这看起来像是一个要么全有要么全无的交易。您可能不会只是将几个数字更改为单位;重点是项目内的所有数据都适当地标有一个单元,以避免任何歧义。这意味着告别使用普通的双精度和整数,现在每个变量都被定义为“单位”或“长度”或“米”等。人们真的会大规模这样做吗?所以即使你有一个很大的数组,每个元素都应该用一个单位来标记。这显然会对大小和性能产生影响。

Despite all the cleverness in trying to push the unit logic into the background, some cumbersome notation seems inevitable with C#. F# does some behind-the-scenes magic that better reduces the annoyance factor of the unit logic.

尽管尝试将单元逻辑推入后台非常聪明,但使用 C# 似乎不可避免地会出现一些繁琐的符号。F# 做了一些幕后魔法,可以更好地减少单元逻辑的烦恼因素。

Also, how successfully can we make the compiler treat a unit just like an ordinary double when we so desire, w/o using CType or ".Value" or any additional notation? Such as with nullables, the code knows to treat a double? just like a double (of course if your double? is null then you get an error).

此外,我们如何成功地让编译器在我们希望的情况下将一个单元当作普通的 double 来对待,而不使用 CType 或“.Value”或任何其他符号?例如对于可空值,代码知道要处理双精度值吗?就像双精度(当然,如果您的双精度?为空,那么您会收到错误消息)。

回答by Mafu Josh

I really liked reading through this stack overflow question and its answers.

我真的很喜欢通读这个堆栈溢出问题及其答案。

I have a pet project that I've tinkered with over the years, and have recently started re-writing it and have released it to the open source at http://ngenericdimensions.codeplex.com

我有一个多年来一直在修补的宠物项目,最近开始重新编写它并将其发布到http://ngenericdimensions.codeplex.com 上的开源项目

It happens to be somewhat similar to many of the ideas expressed in the question and answers of this page.

它恰好与本页问答中表达的许多想法有些相似。

It basically is about creating generic dimensions, with the unit of measure and the native datatype as the generic type placeholders.

它基本上是关于创建通用维度,使用度量单位和本机数据类型作为通用类型占位符。

For example:

例如:

Dim myLength1 as New Length(of Miles, Int16)(123)

With also some optional use of Extension Methods like:

还有一些扩展方法的可选使用,例如:

Dim myLength2 = 123.miles

And

Dim myLength3 = myLength1 + myLength2
Dim myArea1 = myLength1 * myLength2

This would not compile:

这不会编译:

Dim myValue = 123.miles + 234.kilograms

New units can be extended in your own libraries.

可以在您自己的库中扩展新单元。

These datatypes are structures that contain only 1 internal member variable, making them lightweight.

这些数据类型是仅包含 1 个内部成员变量的结构,使它们变得轻量级。

Basically, the operator overloads are restricted to the "dimension" structures, so that every unit of measure doesn't need operator overloads.

基本上,运算符重载仅限于“维度”结构,因此每个度量单位都不需要运算符重载。

Of course, a big downside is the longer declaration of the generics syntax that requires 3 datatypes. So if that is a problem for you, then this isn't your library.

当然,一个很大的缺点是需要 3 种数据类型的泛型语法声明较长。所以如果这对你来说是个问题,那么这不是你的图书馆。

The main purpose was to be able to decorate an interface with units in a compile-time checking fashion.

主要目的是能够以编译时检查的方式用单元装饰接口。

There is a lot that needs to be done to the library, but I wanted to post it in case it was the kind of thing someone was looking for.

图书馆有很多工作要做,但我想张贴它,以防它是有人正在寻找的那种东西。

回答by Henning

Now such a C# library exists: http://www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

现在存在这样一个 C# 库:http: //www.codeproject.com/Articles/413750/Units-of-Measure-Validator-for-Csharp

It has almost the same features as F#'s unit compile time validation, but for C#. The core is a MSBuild task, which parses the code and looking for validations.

它具有与 F# 的单元编译时验证几乎相同的功能,但适用于 C#。核心是一个 MSBuild 任务,它解析代码并寻找验证。

The unit information are stored in comments and attributes.

单位信息存储在注释和属性中。