Extending the .NET type system so the compiler enforces semantic meaning of primitive values in certain cases
我现在正在开发一个系统,它处理具有相同基本.NET类型(double/string/int)的语义不同的值之间的大量转换。这意味着,通过不转换或转换太多次,您可能会对正在使用的"语义类型"感到困惑。理想情况下,如果我试图使用一个在语义上没有意义的值,我希望编译器发出一个警告/错误。
一些例子表明我所指的是:
- 角度可以用度数或弧度表示,但两者都用double表示。
- 矢量位置可以是局部/全局坐标,但两者都由Vector3D结构表示。
- 假设一个SQL库接受各种查询参数作为字符串。最好有一种方法来强制只允许在运行时传入干净的字符串,而获得干净字符串的唯一方法是通过一些防止SQL注入攻击的逻辑。
我相信F有一个编译时的解决方案(称为度量单位),我想在C中做一些类似的工作,尽管我不需要F中度量单位提供的维度分析。
我相信C++可以用EDCOX1 2来实现这一点(虽然我不是C++专家)。
显而易见的解决方案是将double/string/无论如何包装成一个新的类型,以提供编译器需要的类型信息。我很好奇是否有人有其他的解决方案。如果您确实认为包装是唯一/最好的方法,那么请探讨模式的一些缺点(以及我没有提到的任何优点),我特别关注运行时计算中抽象的原始数字类型的性能,因此无论我提出什么解决方案,无论是在内存分配方面,都必须是轻量级的。呼叫调度。
- 测量单位的可能副本,单位为C-几乎
- f中的afaik度量单位只是编译器技巧。尝试在示例程序中使用它们,然后将其分解为C。我想它会给你很好的提示,它是如何实现的。
- @安德烈-每一个语言特性都只是一个编译器技巧。这就是重点
- @斯科特,我认为@andrey所说的是,这个技巧在f_编译之后就消失了。具体来说,在f_中没有对该单元的运行时检查,因为在clr上所有.NET语言中都有类型的运行时检查。
- @汉斯,我不认为这是复制品。我不需要F所做的尺寸分析。这种简化的假设使得更容易和更合理的解决方案成为可能。我只提到了计量单位,以使我的解释更清楚。
- @斯科特·温斯坦不完全是。有些特性是编译器的特性,有些特性是框架的特性。例如-在PerlRegex中是语言特性,C是框架特性。与C++模板和C.net(.NET)泛型相同。
我想您可以创建两个不同的结构来强制类型检查。在下面的代码中,我添加了一个从弧度到double的隐式强制转换和一个从degress到弧度的显式强制转换。您可以使用您喜欢的任何一组隐式和显式操作符,但是我认为我在这里定义的那些操作符会很好地工作,因为弧度结构可以直接传递到Math函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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编译器就应该内联调用。我不认为你会注意到很大的不同,但这绝对是一件值得考虑的事情。
我真的很感兴趣编译器在混合弧度和度数时如何给出警告。它们都是双份的吗?你在OOP世界,所以你应该走这条路。两个建议:
内部只使用一个单位,我认为弧度更好。然后只在输入/输出时转换。
创建结构Degree和Radian并定义转换规则。或者创建类"angle",并在那里保存有关单位和转换的所有信息。
- 1)不幸的是,我不能在内部只使用一种类型。无论如何,我试图实现的是一种编译时的方法来强制执行它,以避免出错。2)这就是我所说的显而易见的方式。我真正想知道的是人们在野外对此做了些什么,它对性能有什么影响,以及我可能没有想到的其他考虑因素。数学实际上是我正在编码的机器人的基础,所以它必须快,否则最坏情况下它会掉下来,或者最好吃进策略周期。
- @DrewNoakes,在第一条评论中检查链接,我认为这很有用
- 我确实看过那个链接。对于我想做的事情来说,那里的解决方案太昂贵了。我不想仅仅为了访问一个数值而进行虚拟调用。我已经考虑过了。我一般同意不反对OO,但我说的是原始价值观。如果OO抽象对于数字——最终是CPU级的数据——非常有效,那么为什么我们在.NET中有结构呢?继承和虚拟调用对它们不可用。我唯一能想到的解决方案是组合:在另一个结构中包装一个数字。感觉更像是C而不是OOP。
- 我最后得到了一个Angle类,它有一个单double类,用弧度表示角度。它有静态工厂方法Angle.FromDegrees(180)和Angle.FromRadian(Math.PI)。另外,我发现将Sin、Cos和Tan属性放在类型上也使我的代码更容易阅读。我超越了所有相关的操作人员,使其更容易操作。我还做了一个互补的AngularSpeed类型,它存储了随时间变化的角度,并使运算符重载使用TimeSpan在这些类型之间转换。似乎运行良好,代码看起来正常。
- @德鲁·诺克斯:oop vs fp:1-0
我从来没有在C中找到一个令人满意的解决方案。似乎类型系统不是为这个用例设计的。如果我现在有同样的需求,我会研究f度量单位方法。
- 很明显,它今天不能做这种事。F在编译器中使用逻辑,我认为编写自己的C编译器是不可行的,或者当前的编译器是可扩展的。我也不想用f重写整个过程,因此这个问题。