为什么要使用隐式/显式转换而不是构造函数?

8
一个例子是:
XNamespace ns = "my namespace"

为什么不呢?
XNamespace ns = new XNamespace ( "my namespace" )

使用隐式/显式转换而不是构造函数的背后的想法是什么?方便吗?
这方面有指导方针吗?
5个回答

10

方便吗?

大体上是的。考虑这样一种情况,当您有一个类似数字的对象(比如一个Complex)需要进行计算时。很明显,编写以下代码:

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

强制类型转换会使代码难以阅读,因此可以使用隐式类型转换来改善这种情况(在本例中的另一种替代方法是使用运算符重载,但这样会导致大量相似的重载)。

有没有指导方针?

尽可能提供少量的隐式类型转换,因为它们可能会隐藏问题。隐式类型转换会在同等程度上减少显式性并增加简洁性。有时这是好的,但有时不是。

我发现最好将隐式类型转换限制在非常相似的类型之间,例如我的示例中类似于数字的对象:从数学上讲,一个int实际上是一个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);
    }
}

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


1
@Konrad:你可以双向创建隐式转换,但是实现两种方式都不明智,因为循环的隐式转换会导致歧义。简单的例子; 抽象类A有两个子类B和C,它们之间可以相互隐式转换。 myA = myB ?? myC; 会让编译器困惑;你是要将myA设置为B类实例还是C类实例? - KeithS
请注意,并非所有的扩展转换都是无损的。如果值超出范围+/-2^53,则long->double的转换可能会丢失数据。 - supercat
@supercat: true。IEEE是特殊的。但说实话,出于这个原因,我个人不认为这是一种扩展转换,我可能也不会将其作为隐式转换。 - Konrad Rudolph
@Konrad Rudolph:我不明白为什么 long->double 的转换应该比 double->single 的转换更宽松,但是如果让我选择的话,两个都应该被视为扩展。在使用浮点数时,几乎任何操作都可能失去精度。如果有人要使用多个短浮点操作数进行一堆数学计算,我宁愿编译器使用双精度进行中间计算,并将结果强制转换为浮点数,而不是使用浮点数进行中间计算。除非事情发生了变化,否则CPU 更喜欢使用双精度,那么为什么不使用它们呢? - supercat
@supercat: 但再次说,这是真的。 - Konrad Rudolph
显示剩余4条评论

5

使用隐式转换将XName等简单类型与方法一起使用的原因之一是方便调用方法。

例如,您可以编写以下代码:

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

提取数据的简便性是LINQ的核心,如果我们必须手动编写代码来提取数据,那么将会非常繁琐。
var info = root.Elements (new XName ("user")).Element (new XName ("info")).Value;

即使是最简单的查询,使用LINQ进行复杂查询也完全值得。另一个重要问题是XNames被原子化了。可以在MSDN上查看:MSDN
“XName对象保证是原子化的;也就是说,如果两个XName对象具有完全相同的命名空间和局部名称,则它们将共享相同的实例。等号和比较运算符也明确提供了这个用途。除其他好处外,此功能还允许更快速地执行查询。在过滤元素或属性名称时,谓词中表达的比较使用标识比较,而不是值比较。确定两个引用实际上指向同一对象比比较两个字符串要快得多。”
你不能在构造函数中提供原子化,但定义一个转换允许您从池中选择相应的对象,并将其返回,就好像它是一个新实例。

3
使用隐式/显式转换是方便的问题,许多编程指南建议您避免使用它们,而是使用显式的ConvertToXXX方法。
其中一个问题是隐式/显式转换进一步重载了强制类型转换运算符的函数。这使它具有双重目的:
  • 通过对象层次结构中的不同类型/接口查看相同对象
  • 将对象转换为全新的类型
不幸的是,在其他领域(如基元和装箱)中,C#已经做到了后者。

2
如果两个类之间需要相互转换,但它们没有共享一个允许此行为的基类或接口,那么可以使用类型转换。隐式转换不应该有任何可能造成数据丢失的情况;它们通常被认为是“扩大”转换。例如,将一个int转换为long是一种扩大转换,转换是隐式进行的,这种转换本质上没有问题。显式转换可能涉及到数据丢失的可能性;一个long类型的值可能或可能不会转换为int类型。
我用隐式转换的一个技巧是在没有其他合理选项时将不同命名空间中的类相互转换。例如,一个WCF服务返回一个AuthenticationToken对象,我需要将其传递给另一个命名空间中的WCF服务。两个服务都有这个AuthenticationToken对象,而且经常进行转换会很麻烦。我的解决方案是在partial类中使用public static implicit operator添加相互转换的功能。

“隐式转换不应该导致数据丢失”这种说法很流行,但我认为更好的说法是“往返隐式转换不应该导致数据丢失”。如果一个SimpleType表示一个所有“复杂”属性都匹配默认值的ComplicatedType,则只应该允许从SimpleTypeComplicatedType的扩展转换,但如果它表示一个具有未知复杂属性值的ComplicatedType,则隐式转换应该只能反向允许。 - supercat

1

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接