【问题标题】:What's the reason of using implicit/explicit convertions instead of constructors?使用隐式/显式转换而不是构造函数的原因是什么?
【发布时间】:2011-04-18 11:00:52
【问题描述】:

一个例子是:

XNamespace ns = "my namespace"

为什么不呢?:

XNamespace ns = new XNamespace ( "my namespace" )

使用隐式/显式转换而不是构造函数背后的想法是什么?方便吗?

有这方面的指导方针吗?

【问题讨论】:

    标签: c# .net constructor type-conversion base-class-library


    【解决方案1】:

    方便吗?

    或多或少,是的。考虑当您有一个类似数字的对象(例如,Complex)进行计算时的情况。显然,编写代码如:

    Complex result = c1 * new Complex(2) + new Complex(32);
    

    非常烦人且难以阅读。隐式转换在这里有所帮助(在此示例中,替代方法是运算符重载,但这会导致许多类似的重载)。

    有这方面的指导方针吗?

    提供尽可能少的隐式转换,因为它们可能会隐藏问题。隐式转换减少显式性的程度与它们增加简洁性的程度相同。有时这很好,但有时不是。

    我发现最好将隐式转换限制为非常相似的类型,例如我上面示例中的类似数字的对象:int 本质上是-a Complex(来自数学观点;即使它不是通过继承建模的),因此隐式转换是有意义的。

    在 VB 中,隐式转换称为“Widening”(与 Narrowing 相反,即explicit),这很好地描述了它:在转换过程中不会丢失任何信息。

    此外,操作符本质上是一个构建器函数,与构造器相比,它具有(部分)构建器函数的通常优势:即,它可以重用缓存值,而不是总是创建新实例。

    以我的Complex 为例。我们可能希望缓存常用复数的值:

    Class Complex {
        // Rest of implementation.
    
        private static Complex[] cache = new[] {
            new Complex(-1), new Complex(0), new Complex(1) };
    
        public implicit operator Complex(int value) {
            if (value >= -1 && value <= 1)
                return cache[value];
            else
                return new Complex(value);
        }
    }
    

    当然,这种微优化是否有效是另一个问题。

    【讨论】:

    • 谢谢,您将如何处理有助于复杂类型的运算符重载?您需要能够编辑 int 类型,对吧?
    • @Joan:不,因为您可以双向实现运算符:from 您的自定义类型和 to 您的自定义类型。在我的示例中,只需让 Complex 实现运算符 implicit operator Complex(int value)
    • @Konrad:您可以通过两种方式创建隐式转换,但以两种方式实现它们是不明智的,因为循环隐式转换会导致歧义。简短的例子; abstract A 有子 B 和 C,它们可以隐式地转换为另一个。我的A =我的B ??我的C;会混淆编译器;您是将 myA 设置为 B 还是 C 的实例?
    • @Konrad,此外,在 Complex 的特定情况下,int(甚至 double)和 Complex 值之间没有一一对应的关系。我使用双向隐式转换的唯一一次是当新类型实际上只是原始类型的“命名”实例时,例如“Force”类型,它只是一个带有添加概念约束的双精度,它意味着“a以牛顿为单位的力。即便如此,有时将一个转换明确化以展示如何解释特定值是有帮助的。
    • @KeithS, @Dan:你说得对。我并不是建议你应该以两种方式实现它们。以 Complex 为例:显然,隐式转换 int -> Complex 是有意义的,因为存在精确映射,而相反则没有意义,因为会丢失信息。
    【解决方案2】:

    我相信对 XName 等简单类型使用隐式转换的原因之一是调用方法的方便。

    例如,你可以写

    var info = root.Elements ("user").Element ("info").Value;
    

    提取数据的简单性是 LINQ 的全部意义所在,如果我们必须编写的话

    var info = root.Elements (new XName ("user")).Element (new XName ("info")).Value;
    

    即使对于最简单的查询,LINQ 对于复杂的查询是否完全值得?

    这里的另一个重要问题是 XName 是原子化的。 见MSDN

    XName 对象保证被原子化;也就是说,如果两个 XName 对象具有完全相同的命名空间和完全相同的本地名称,它们将共享同一个实例。为此,还明确提供了相等和比较运算符。

    除其他好处外,此功能还可以更快地执行查询。在过滤元素或属性的名称时,谓词中表示的比较使用身份比较,而不是值比较。确定两个引用实际上是指同一个对象比比较两个字符串要快得多。

    您不能在构造函数中提供原子化,但是定义一个转换可以让您从池中挑选相应的对象并将其返回,就像它是一个新实例一样。

    【讨论】:

      【解决方案3】:

      使用隐式/显式转换是方便的问题,许多编程指南建议您避免使用显式ConvertToXXX 方法。

      其中一个问题是隐式/显式转换的使用进一步重载了强制转换运算符的函数。它赋予了它双重目的

      • 通过对象层次结构中的不同类型/接口查看同一对象
      • 将对象完全转换为新类型

      不幸的是,C# 已经在其他领域(使用原语和装箱)实现了后者。

      【讨论】:

        【解决方案4】:

        如果两个类应该可以相互转换,但它们不共享自动允许这种行为的基类的接口,则可以使用转换。隐式转换应该绝不有数据丢失的可能性;它们通常被认为是“扩大”转换。例如,将int 转换为long 是一种扩展转换,并且隐式转换没有固有的问题。显式转换可能涉及数据丢失的可能性; long 可能会也可能不会转换为 int,具体取决于其值。

        我使用隐式转换的一个技巧是当我没有其他合理的选择时,将不同命名空间中的类相互转换。例如,一个 WCF 服务返回一个 AuthenticationToken 对象,我需要将该对象传递给不同命名空间中的 WCF 服务。两者都有这个 AuthenticationToken 对象,并且不断的转换会很痛苦。我的解决方案涉及在部分类中使用public static implicit operator 来添加功能以进行各种方式的转换。

        【讨论】:

        • “隐式转换不应导致数据丢失”语言的概念很流行,但我认为更好的概念是“往返隐式转换不应导致数据丢失”。如果SimpleType 表示ComplicatedType,其中“复杂”属性都匹配默认值,则应仅允许从SimpleTypeComplicatedType 的扩大转换,但如果它表示复杂属性值未知的ComplicatedType ,如果有的话,应该只允许隐式转换。
        【解决方案5】:

        就个人而言,当我知道 rhs 可以转换为类的静态成员时,我会使用转换(比如说 color = "red" 来暗示 color = Colors.Red

        当我打算实际创建一个新实例时,我使用 new 运算符。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-12-21
          • 1970-01-01
          • 1970-01-01
          • 2012-06-28
          • 1970-01-01
          • 2018-10-29
          • 2015-11-23
          • 2018-08-03
          相关资源
          最近更新 更多