【问题标题】:Why can '=' not be overloaded in C#?为什么'='不能在C#中被重载?
【发布时间】:2010-10-10 14:33:00
【问题描述】:

我想知道,为什么我不能在 C# 中重载“=”?我能得到更好的解释吗?

【问题讨论】:

  • 您是否有具体案例可以与我们分享,您希望在其中重载 = ?
  • @Neil N 例如,我需要为左侧和右侧的不同类型的点重载 operator = 。我已经定义了我自己的类 MyPoint {double X, double Y} 并且我希望重载 operator = 能够从右侧的 System.Windows.Point 复制 X 和 Y。 static public MyPoint operator = (System.Windows.Point winPt) {...} 在我看来是个好主意。
  • @Peter,听起来您想 CLONE 一个对象,而不是像 = 运算符那样进行引用复制。

标签: c# operator-overloading


【解决方案1】:

覆盖赋值的类型

覆盖赋值有两种类型:

  1. 当您觉得用户可能会错过某些内容,并且您希望强制用户使用“投射”时 当你丢失浮点值时,就像浮点数到整数一样

int a = (int)5.4f;

  1. 当您希望用户这样做时,甚至没有注意到他/她更改了对象类型

float f = 5;

如何覆盖分配

对于1,使用explicit关键字:

public static explicit override ToType(FromType from){
    ToType to = new ToType();
    to.FillFrom(from);
    return to;
}

对于2,使用implicit关键字:

public static implicit override ToType(FromType from){
    ToType to = new ToType();
    to.FillFrom(from);
    return to;
}

更新:

注意:此实现可以在 FromTypeToType 类中进行,具体取决于您的需要,没有限制,您的一个类可以保存所有转换,而另一个实现没有代码.

【讨论】:

    【解决方案2】:

    这段代码对我有用:

    public class Class1
    {
    
        ...
    
        public static implicit operator Class1(Class2 value)
        {
            Class1 result = new Class1();
    
            result.property = value.prop;
    
            return result;
    
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      实际上,如果您可以定义具有值语义的类并在堆栈中分配这些类的对象,那么重载operator = 将是有意义的。但是,在 C# 中,你不能。

      【讨论】:

      • 实际上,如果您可以定义具有值语义的类,那将是有意义的,即使此类类的实例总是必须在堆上分配。
      • @supercat:完全同意。但请放过我一点:那时我不知道比 C++ 更重视语义的语言。
      • 我从来没有使用过任何比 C++ 更重视语义的语言,而 .NET 要在任何地方支持它们也需要它更加复杂。尽管如此,如果有一种方法可以让值类型指定不应允许类型本身之外的代码装箱或复制它,那将是有帮助的。如果存在这样的事情,那么让= 调用类型中的方法将非常有用。许多适用于任何类型的东西将无法适用于这种特殊类型,但许多类型的错误可能会在编译时产生问题。
      • 回想起来,我不会对过去的自己有所懈怠。如果您需要重载赋值运算符,那么相等的含义在您的语言中是不正确的。几十年来,机器学习一直对平等的含义给出正确的答案。即使在今天,语言设计者仍坚持错误是不可接受的。
      • 即使按位相等足以保证值相等,但这并不意味着具有不同位模式的对象也不应该相等。例如,如果x 持有对一个包含"hello" 的字符串对象的引用,而y 持有对另一个包含"hello" 的字符串对象的引用,即使它们持有对不同的对象。能够重载赋值运算符对于某些可以按位复制工作的类型可能很有用,但可能不是复制值的最佳方式。
      【解决方案4】:

      内存管理语言通常使用引用而不是对象。当您定义一个类及其成员时,您是在定义对象的行为,但是当您创建一个变量时,您是在使用对这些对象的引用。

      现在,运算符 = 应用于引用,而不是对象。当您将引用分配给另一个引用时,您实际上是在使接收引用指向与另一个引用相同的对象。

      Type var1 = new Type();
      Type var2 = new Type();
      
      var2 = var1;
      

      在上面的代码中,在堆上创建了两个对象,一个由 var1 引用,另一个由 var2 引用。现在最后一条语句使 var2 引用指向 var1 所引用的同一对象。在那一行之后,垃圾收集器可以释放第二个对象,并且内存中只有一个对象。整个过程中,对对象本身没有任何操作。

      回到为什么 = 不能被重载,系统实现是你可以对引用做的唯一明智的事情。您可以重载应用于对象的操作,但不能重载引用。

      【讨论】:

      • 谢谢,但我看不到,这是如何回答问题的?
      • @user492238: operator= 不是对对象的操作,而是对引用的操作。您可以重载应用于 objects 的运算符,但不能重新定义应用于 references 的运算符,在这种特殊情况下,这样做会改变语言的语义: reference 类型将表现为 value 类型。
      • @DavidRodríguez-dribeas 这是一个经常重复的声明,但它是一个重言式 - 它重申了事实,但没有提供任何推理。在 C# 中,operator= 的行为与 static void assign(ref Type dst, in Type src) { dst = src; } 完全相同。我们怎么知道?您可以对 C# 源代码进行机器翻译,并将所有分配(assign 本身中的分配除外 :) 替换为 assign 的调用,程序将执行相同的操作。哎呀,它都会被内联并且没有开销(我已经尝试过了)。可惜语言没有暴露这一点。
      【解决方案5】:

      能够为赋值操作定义特殊语义会很有用,但前提是这种语义可以应用于将给定类型的一个存储位置复制到另一个存储位置的所有情况。尽管标准 C++ 实现了这样的分配规则,但它要求在编译时定义所有类型。将反射和泛型添加到列表中时,事情会变得更加复杂。

      目前,.net 中的规则指定存储位置可以设置为其类型的默认值——无论该类型是什么——通过将所有字节清零。他们进一步指定可以通过复制所有字节将任何存储位置复制到另一个相同类型的位置。这些规则适用于所有类型,包括泛型。给定KeyValuePair<t1,t2> 类型的两个变量,系统可以将一个变量复制到另一个变量,而无需知道该类型的大小和对齐要求。如果 t1t2这些类型中的任何字段的类型 有可能实现复制构造函数,则将一个结构实例复制到另一个的代码必须是复杂得多。

      这并不是说这种能力提供了一些显着的好处——如果设计一个新的框架,自定义值赋值运算符和默认构造函数的好处可能会超过成本。然而,实施成本在新框架中将是巨大的,并且对于现有框架而言可能无法克服。

      【讨论】:

      • 谁提到了副本? C++ 中的operator= 只允许复制有意义的状态的一部分。假设您有一个限制其值的有界类型。在 C++ 中,当您从某个数字类型分配给此类类型时,您可以让分配进行钳位。这在 C# 中是不可能的,而且很痛苦;您需要使用显式设置方法。使用安全代码在 C# 中实现“自然”类型安全单元是不可能的:((好吧,你可以,如果你强迫用户永远不要做分配!)。
      • @KubaOber:在 C++ 中,如果模板化结构类型 Woozle<T> 包含类型为 T 的成员,它覆盖了赋值运算符,那么在 Woozle<T> 上使用赋值运算符将导致 T 的赋值运算符用于其中包含的T。但是,.NET Framework 没有递归处理结构分配的机制。相反,它只是将所有结构内容和填充的大小相加,并盲目复制那么多字节。即使 C# 允许覆盖赋值运算符...
      • ...对于某些结构类型Widget,它将无法覆盖复制包含一个结构的代码的行为,将一个传递给包含一个的类的构造函数,等等. 诸如整数值钳位之类的事情可以通过扩大或缩小转换运算符来完成,但为了支持可覆盖赋值运算符可能有用的大多数目的,需要 .NET 从一开始就将值类型分为三个不同的类别:那些可以简单赋值的,那些有虚拟赋值运算符的,还有那些没有......
      • ...任何赋值运算符(后一种形式的泛型类型参数只能用于声明ref 参数,如果没有其他接口约束,通常不会很有用)。我认为如果产品能够承受进入市场所需的额外时间,这样的设计可能是可行的,但我不怪微软推出了一个更简单的设计。
      【解决方案6】:

      因为这样做没有任何意义。

      在 C# 中 = 将对象引用分配给变量。所以它对变量和对象引用进行操作,而不是对象本身。根据对象类型重载它是没有意义的。

      在 C++ 中,定义 operator= 对于可以创建实例的类是有意义的,例如在堆栈上,因为对象本身存储在变量中,而不是对它们的引用。因此,定义如何执行此类分配是有意义的。但即使在 C++ 中,如果您有一组通常通过指针或引用使用的多态类,您通常通过将 operator= 和复制构造函数声明为私有(或从 boost::noncopyable 继承)来明确禁止像这样复制它们,因为与您不在 C# 中重新定义 = 的原因完全相同。简单地说,如果你有 A 类的引用或指针,你真的不知道它是指向 A 类的实例还是 A 的子类 B 类。那么你真的知道在这种情况下如何执行 = 吗?

      【讨论】:

      • 好的,使用您的术语,然后重载“引用赋值运算符”是有意义的。以下场景:您希望收到有关正在更新/更改的集合中的引用的通知。所以 collection[0] = new Value()... 所以一些框架可以正确地更新数据绑定,而不需要丑陋的代码解决方法。您只需编写一个通用的 Observable : T 并覆盖该运算符,您就可以做任何您想做的事情。 C# 甚至可以限制你,所以你真的必须分配值。不可能有物理定律阻止它起作用
      • @ecreif:我不确定您真正要问的是什么,但如果我们仍在 CLI/.NET/C# 的现实中,我会说“不”。虽然总的来说这可能是一个有趣的问题;如果您可以再考虑一下,然后将其改写为一个问题,可以在这里单独提出,也可以由程序员提出。
      • @ecreif:至于您的场景:“您希望收到通知,以获取正在更新/更改的集合中的引用。”我会更多地把它看作是一个集合的一个特征。实现一个带有通知的集合,并将其余代码设计为使用接口而不是具体集合(例如 IList 代替 List,IDictionary 代替 Dictionary),以便能够切换到您的特殊集合。
      • @ecreif:深思熟虑:“重载引用赋值运算符”是什么意思?它会超载什么?什么都没有:所有的任务?对特定类型的引用分配?静态类型:基于编译时引用类型?还是运行时类型:基于引用对象的实际类型?或者,也许您想将它基于拥有该引用的对象?但是你需要吗?你要吗?对每个分配进行运行时类型检查的成本如何?编译器优化分配怎么样?那么关于程序正确性的推理呢?
      • 这是一个声明。在我的场景中,您将如何实现该观察者?我想听听收藏内容的变化。查看逻辑层次结构,此更改不属于任何“实体”。它不属于集合,因为集合没有被触及或调整大小,它不属于 Observable 因为它也没有被触及。所触及的是集合内的引用。要获得更改通知,我想覆盖 Observable 的引用赋值运算符。应在分配发生时调用。
      【解决方案7】:

      因为射中自己的脚是不受欢迎的。

      在更严肃的情况下,人们只能希望您的意思是比较而不是分配。该框架对干扰相等/等价评估做出了详尽的规定,请在帮助中或通过 msdn 在线查找“比较”。

      【讨论】:

        【解决方案8】:

        您可以在 C# 中重载赋值。不是在整个对象上,而是在它的成员上。你用 setter 声明一个属性:

        class Complex
        {
            public double Real
            {
                get { ... }
                set { /* do something with value */ }
            }
        
            // more members
        }
        

        现在,当您分配给 Real 时,您自己的代码就会运行。

        对一个对象的赋值是不可替换的,因为它已经被语言定义为意味着非常重要的东西。

        【讨论】:

          【解决方案9】:

          在 C++ 中是允许的,如果不小心,可能会导致很多混乱和寻找错误。

          这篇文章非常详细地解释了这一点。

          http://www.relisoft.com/book/lang/project/14value.html

          【讨论】:

          • C++ 正统的规范形式,参见 Coplien 和 Stroustrup,它要求用户定义类型的赋值运算符表现得像内置类型一样稳健,从而产生干净的代码,没有错误。
          【解决方案10】:

          一种可能的解释是,如果重载赋值运算符,则无法进行正确的引用更新。它实际上会搞砸语义,因为当人们期望更新的引用时,您的 = 运算符也可能完全在做其他事情。对程序员不太友好。

          您可以使用隐式和显式 to/from 转换运算符来减轻无法重载赋值的一些看似缺点。

          【讨论】:

          • 好的,但是对于 Equals() 或 ToString() 或 | 的重载,该论证同样适用。还是...?
          • 顺便说一句,vb6 确实允许在某些情况下有效地覆盖赋值运算符;如果需要参考分配,可以使用Set xxx=yyy。当应用于变体类型时,这会导致一些真正可怕的语义。
          【解决方案11】:

          如果您重载了 '=',您将永远无法在创建对象引用后更改它。 ...考虑一下-重载运算符中对 theObjectWithOverloadedOperator=something 的任何调用都会导致对重载运算符的另一个调用...那么重载运算符到底在做什么?也许设置一些其他属性 - 或将值设置为新对象(不变性)? 通常不是 '=' 的含义..

          但是,您可以覆盖隐式和显式转换运算符: http://www.blackwasp.co.uk/CSharpConversionOverload.aspx

          【讨论】:

          • 使用这种可重载的 = 运算符不是用引用指向的对象来做某事,而是“知道”某些东西已分配给变量。这在需要值类型语义和/或立即处理(至少)的场景中很重要。
          【解决方案12】:

          我认为没有任何特别的理由可以指出。一般来说,我认为这个想法是这样的:

          • 如果您的对象是一个大而复杂的对象,则使用= 运算符执行非赋值操作可能会产生误导。

          • 1234563确实。)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-06
            • 2012-01-14
            • 2022-01-26
            • 2011-05-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多