【问题标题】:Extending the .NET type system so the compiler enforces semantic meaning of primitive values in certain cases扩展 .NET 类型系统,以便编译器在某些情况下强制执行原始值的语义含义
【发布时间】:2011-02-26 02:11:50
【问题描述】:

我现在正在开发一个系统,该系统处理语义上不同的值之间的大量转换,这些值具有相同的原始 .NET 类型(double/string/int)。这意味着您可能会因为不转换或转换太多次而对您使用的“语义类型”感到困惑。理想情况下,如果我尝试使用在语义上没有意义的值,我希望编译器发出警告/错误。

一些例子来说明我指的是什么:

  • 角度可以以度或弧度为单位,但两者都由double 表示。
  • 向量位置可能在局部/全局坐标中,但两者都由Vector3D 结构表示。
  • 想象一个接受各种查询参数作为字符串的 SQL 库。最好有一种方法来强制在运行时只允许传入干净的字符串,而获得干净字符串的唯一方法是通过一些 SQL 注入攻击防止逻辑。

我相信 F# 对此有一个编译时解决方案(称为度量单位。)我想在 C# 中做类似的事情,尽管我不需要 F# 中的度量单位提供的维度分析。

我相信 C++ 可以使用 typedef 实现这一点(尽管我不是 C++ 专家)。

显而易见的解决方案是将 double/string/whatever 包装在一个新类型中,以提供编译器所需的类型信息。我很好奇是否有人有替代解决方案。如果您确实认为换行是唯一/最好的方法,那么请谈谈该模式的一些缺点(以及我也没有提到的任何优点。)我特别关心抽象原始数字类型在我的运行时计算,所以无论我想出什么解决方案,无论是在内存分配还是调用调度方面,都必须是轻量级的。

【问题讨论】:

  • F# 中的 AFAIK 度量单位只是编译器技巧。尝试在示例程序中使用它们,然后将其反编译为 C#。我想它会给你很好的提示它是如何实现的。
  • @Andrey - 每个 语言特性只是一个编译器技巧。这就是重点
  • @Scott,我认为@Andrey 的意思是在 F# 中编译后这个技巧就消失了。具体来说,在 F# 中没有对单元的运行时检查,因为在 CLR 上会对所有 .NET 语言中的类型进行运行时检查。
  • @Hans,我不认为这是重复的。我不需要 F# 的维度分析。这种简化的假设使解决方案变得更容易和更站得住脚。我只提到计量单位是为了让我的解释更清楚。

标签: c# .net performance type-systems


【解决方案1】:

我猜你可以创建两个不同的结构来强制类型检查。在下面的代码中,我添加了从弧度到 double 的隐式转换和从 degress 到弧度的显式转换。您可以使用您喜欢的任何一组隐式和显式运算符,但我认为我在这里定义的那些会很好用,因为 Radians 结构可以直接传递到 Math 函数中。

public struct Degrees
{
  private double m_Value;

  public static explicit operator Radians(Degrees rhs)
  {
    return rhs.m_Value * (Math.Pi / 180);
  }
}

public struct Radians
{
  private double m_Value;

  public static implicit operator double(Radians rhs)
  {
    return rhs.m_Value;    
  }
}

【讨论】:

  • 这是我正在考虑的一个选项,但我认为我会明确所有转换(在我的情况下)以避免事故,并且需要明确构造任何一种类型:elbow.MoveTo(new Degrees(180)); 你认为那里像这样包装double 会对性能造成很大影响吗?在计算的最后,我需要取消引用 Value 属性。我希望在 JIT 深处的某个地方,这基本上被转换为等同于直接使用 double,但我无法在脑海中完全想象它。
  • 关于性能……这是个好问题。我也在考虑这个问题。只要您使方法和属性访问器保持简短,JIT 编译器就应该内联调用。我认为您不会注意到太大的不同,但这绝对是值得考虑的事情。
【解决方案2】:

我从未在 C# 中找到此类问题的令人满意的解决方案。看起来类型系统不是为这个用例设计的。如果我现在有同样的需求,我会研究 F# 度量单位方法。

【讨论】:

  • 显然它今天不具备这种能力。 F#在编译器中使用逻辑,我不认为自己编写C#编译器是可行的,或者当前的编译器是可扩展的。我也不想用 F# 重写整个事情,因此提出了这个问题。
【解决方案3】:

我真的很感兴趣当你混合弧度和度数时编译器如何给出警告。他们都是双标吗?你在 OOP 的世界里,所以你应该走这条路。 两个建议:

  1. 内部只使用一个单位,我认为弧度更好。然后仅在输入/输出上进行转换。\
  2. 创建结构DegreeRadian 并定义转换规则。或者创建“角度”类并在其中保存有关单位和转换的所有信息。

【讨论】:

  • 1) 不幸的是,我不能在内部只使用一种类型。无论如何,我想要实现的是一种强制执行此操作以避免错误的编译时方式。 2)这几乎就是我所说的显而易见的方式。我真正想知道的是人们在野外对此做了什么,它对性能产生了什么样的影响,以及我可能没有想到的任何其他考虑因素。数学真的是我正在编写的机器人的面包和黄油,所以它必须很快,否则最坏的情况它会崩溃,或者最好的情况是吃掉策略周期。
  • @Drew Noakes,查看第一条评论中的链接,我认为它很有用
  • 我确实看过那个链接。那里的解决方案对于我正在尝试做的事情来说太昂贵了。我不想仅仅为了访问数值而进行虚拟调用。我一直在思考这个问题。我一般同意不反对 OO,但我说的是原始值。如果 OO 抽象对于数字(最终是 CPU 级别的数据)非常有效,那么我们为什么要在 .NET 中使用结构呢?继承和虚拟调用对他们​​不可用。我能想到的唯一解决方案是组合:将一个数字包装在另一个结构中。感觉更像 C 而不是 OOP。
  • 我最终得到了一个 Angle 类,它有一个 double 以弧度表示角度。它有静态工厂方法Angle.FromDegrees(180)Angle.FromRadian(Math.PI)。顺便说一句,我发现在类型上放置SinCosTan 属性也使我的代码更易于阅读。我覆盖了所有相关的运算符以使其易于使用。我还创建了一个互补类型AngularSpeed,它存储了角度随时间的变化,并创建了使用TimeSpan 的运算符重载,以在这些类型之间进行转换。似乎表现不错,代码看起来还可以。
  • @Drew Noakes:OOP 与 FP:1 - 0
猜你喜欢
  • 2017-01-17
  • 1970-01-01
  • 1970-01-01
  • 2019-08-07
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 2017-05-03
  • 2019-03-23
相关资源
最近更新 更多