我想知道,为什么我不能在 C# 中重载“=”?我能得到更好的解释吗?
12 回答
内存管理语言通常使用引用而不是对象。当您定义一个类及其成员时,您正在定义对象行为,但是当您创建一个变量时,您正在使用对这些对象的引用。
现在,运算符 = 应用于引用,而不是对象。当您将引用分配给另一个引用时,您实际上是在使接收引用指向与另一个引用相同的对象。
Type var1 = new Type();
Type var2 = new Type();
var2 = var1;
在上面的代码中,在堆上创建了两个对象,一个由 var1 引用,另一个由 var2 引用。现在最后一条语句使 var2 引用指向 var1 所引用的同一对象。在那一行之后,垃圾收集器可以释放第二个对象,并且内存中只有一个对象。在整个过程中,没有对对象本身进行任何操作。
回到为什么 = 不能被重载,系统实现是你可以对引用做的唯一明智的事情。您可以重载应用于对象但不能应用于引用的操作。
如果你重载了'=',你将永远无法在创建对象引用后更改它。...考虑一下-重载运算符中对 theObjectWithOverloadedOperator=something 的任何调用都会导致对重载运算符的另一个调用...那么重载运算符到底在做什么?也许设置一些其他属性 - 或将值设置为新对象(不变性)?通常不是'='所暗示的..
但是,您可以覆盖隐式和显式转换运算符:http: //www.blackwasp.co.uk/CSharpConversionOverload.aspx
因为这样做真的没有意义。
在 C# 中 = 将对象引用分配给变量。所以它对变量和对象引用进行操作,而不是对象本身。根据对象类型重载它是没有意义的。
在 C++ 中,定义 operator= 对于可以在堆栈上创建实例的类是有意义的,因为对象本身存储在变量中,而不是对它们的引用。因此,定义如何执行此类分配是有意义的。但即使在 C++ 中,如果您有一组通常通过指针或引用使用的多态类,您通常通过将 operator= 和复制构造函数声明为私有(或从 boost::noncopyable 继承)来明确禁止像这样复制它们,因为与您不在 C# 中重新定义 = 的原因完全相同。简单地说,如果你有 A 类的引用或指针,你真的不知道它是指向 A 类的实例还是 A 的子类 B 类。那么你真的知道在这种情况下如何执行 = 吗?
实际上,如果您可以定义具有值语义的类并在堆栈中分配这些类的对象,operator =
那么重载将是有意义的。但是,在 C# 中,你不能。
一种可能的解释是,如果重载赋值运算符,则无法进行正确的引用更新。它实际上会搞砸语义,因为当人们期望引用更新时,您的 = 运算符也可能完全在做其他事情。对程序员不太友好。
您可以使用隐式和显式 to/from 转换运算符来减轻无法重载赋值的一些看似缺点。
我不认为有任何真正特别的单一理由可以指出。一般来说,我认为这个想法是这样的:
如果你的对象是一个大而复杂的对象,那么做一些不是用
=
运算符赋值的事情可能会产生误导。如果您的对象是一个小对象,您最好将其设置为不可变并在对其执行操作时返回新副本,以便赋值运算符按照您期望的开箱即用方式工作(就像
System.String
这样做一样。)
您可以在 C# 中重载赋值。不是在整个对象上,而是在它的成员上。您使用 setter 声明一个属性:
class Complex
{
public double Real
{
get { ... }
set { /* do something with value */ }
}
// more members
}
现在,当您分配给 时Real
,您自己的代码就会运行。
分配给一个对象的原因是不可替换的,因为它已经被语言定义为意味着非常重要的东西。
它在 C++ 中是允许的,如果不小心,可能会导致很多混乱和错误搜索。
这篇文章非常详细地解释了这一点。
因为在脚上射击自己是不受欢迎的。
更严肃地说,人们只能希望你的意思是比较而不是分配。该框架为干扰相等/等价评估做出了详尽的规定,请在帮助中或在线与 msdn 中查找“比较”。
能够为赋值操作定义特殊语义将是有用的,但前提是这种语义可以应用于将给定类型的一个存储位置复制到另一个存储位置的所有情况。尽管标准 C++ 实现了这样的分配规则,但它要求在编译时定义所有类型。当反射和泛型添加到列表中时,事情变得更加复杂。
目前,.net 中的规则指定存储位置可以设置为其类型的默认值——无论该类型是什么——通过将所有字节清零。他们进一步指定可以通过复制所有字节将任何存储位置复制到另一个相同类型的位置。这些规则适用于所有类型,包括泛型。给定两个 type 变量KeyValuePair<t1,t2>
,系统可以将一个变量复制到另一个变量,而无需知道该类型的大小和对齐要求。如果t1
,t2
或其中任何一个类型中的任何字段的类型都可以实现复制构造函数,那么将一个结构实例复制到另一个的代码将变得更加复杂。
这并不是说这种能力提供了一些显着的好处——如果设计一个新的框架,自定义值赋值运算符和默认构造函数的好处可能会超过成本。然而,实施成本在新框架中将是巨大的,并且对于现有框架而言可能无法克服。
这段代码对我有用:
public class Class1
{
...
public static implicit operator Class1(Class2 value)
{
Class1 result = new Class1();
result.property = value.prop;
return result;
}
}
覆盖分配的类型
覆盖分配有两种类型:
- 当您觉得用户可能会遗漏某些东西,并且您希望强制用户使用 浮点数到整数等“强制转换”时,当您丢失浮点值时
int a = (int)5.4f;
- 当您希望用户执行此操作时甚至没有注意到他/她更改了对象类型
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;
}
更新:
注意:此实现可以在FromType
orToType
类中进行,具体取决于您的需要,没有限制,您的一个类可以保存所有转换,而另一个实现没有代码。