在C#中,有没有绕过重载赋值运算符的方法?

34

与C++不同,在C#中你不能重载赋值运算符。

我正在为进行大数运算的自定义Number类工作,并希望它具有内置数字类型(如int,decimal等)的外观和感觉。我已经重载了算术运算符,但是赋值运算符依然无法重载...

这里是一个例子:

Number a = new Number(55); 
Number b = a; //I want to copy the value, not the reference

那个问题有没有解决的办法?


2
解释问题(为什么你需要它)。 - Shog9
1
这是一个例子: Number a = new Number(55); Number b = a; 我希望它复制的是值,而不是引用。 - Jon Galloway
你认为自己的“功能过多,无法将其作为结构体”是什么意思?你是指它在内存使用方面太大了吗? - Jon Skeet
@Jon Skeet:不,只是里面有太多的方法……感觉不像一个结构体。 - Jon Galloway
2
看看 DateTime 和 TimeSpan。有很多方法可以使用。如果它是一个有效的原子值,那么它应该是一个结构体。数字几乎总是属于这个类别。 - Jon Skeet
显示剩余6条评论
7个回答

42

你可以使用“implicit”关键字来创建赋值的重载:

假设你有一个名为Foo的类型,你觉得它可以从字符串隐式转换。 你可以在Foo类中编写以下静态方法:

public static implicit operator Foo(string normalString)
{
    //write your code here to go from string to Foo and return the new Foo.
}

完成这一步之后,您可以在代码中使用以下内容:

Foo x = "whatever";

2
很酷,这回答了我要问的问题 ;) - Sam Salisbury
3
如果您尝试将一个类型赋值给另一个类型,那么这种方法只适用于隐式转换。毕竟,这是一种隐式转换。如果您想将“Number”赋值给“Number”,我认为这并没有帮助。 - Drew Noakes
@Jonathan Wood - 那正是我正在寻找的!非常感谢! - Steven Magana-Zook
如果你想将Number赋值给Number,并且Number是一个结构体,那么使用默认的赋值运算符就可以得到期望的效果:按值赋值。 - Nilzor
@Nilzor,在这种情况下,你确实会得到一个值的副本,但是OP想要以某种方式使用自己的代码来控制该过程。我认为除非你在两种类型之间进行适配,否则这是不可能的。因此,它与您在C++中所能做的略有不同。 - Drew Noakes
@jonathon wood - 谢谢。我也在寻找这个。对3D数学非常有帮助。 - user585968

29

我仍然不太清楚你是否真正需要它。 可以有两种选择:

  • 你的 Number 类型应该是一个结构体(这很可能是正确的 - 数字是结构体最常见的例子之一)。 请注意,你想让你的类型像 int、decimal 等所有类型都是结构体。

或者:

  • 你的 Number 类型应该是不可变的,使每个突变操作返回一个新实例,在这种情况下,你不需要在赋值时复制数据。(事实上,无论是否为结构体,你的类型都应该是不可变的。可变结构体很危险,数字绝对不应该是可变的引用类型。)

是的,也许结构体的想法不错。而且我喜欢不可变性选项-这是另一个答案中提出的建议。谢谢,我会尝试你提出的建议。 - Jon Galloway
1
可变结构体可能是有害的,但 C# 团队认为对于枚举器来说还可以接受。我认为更正确的说法是作为一般规则。 - Razor
3
如果可以避免对象共享,可变性并非不好。这可以通过实现线性类型来实现,需要进行某种形式的赋值重载。http://en.wikipedia.org/wiki/Linear_type_system. - cdiggins

6

你不能通过让C++代码看起来像C#来解决这个问题,因为在C++中,a = b; 的语义与在C#中不同。在C#中,a = b; 是将a指向与b相同的对象,而在C++中,a = b会改变a的内容。两者都有好处和坏处。这就像你做的事情一样。

MyType * a = new MyType();
MyType * b = new MyType(); 
a = b; /* only exchange pointers. will not change any content */

在C++中(这将导致对第一个对象的引用丢失,并创建内存泄漏。但让我们在这里忽略它)。你也不能重载赋值运算符。解决方法很简单:
MyType a = new MyType();
MyType b = new MyType();

// instead of a = b
a.Assign(b);

免责声明: 我不是C#开发人员

你可以像这样创建一个只写属性。然后在上面执行 a.Self = b;

public MyType Self {
    set {
        /* copy content of value to this */
        this.Assign(value);
    }
}

现在,这不是一个好的做法。因为它违反了最小惊讶原则(POLS)。如果我们执行a.Self = b;,我们不会预期a会改变。

也许我可以那样做。但我的意思是 - 一种解决方法,可以让我真正地感受到赋值运算符重载的外观和感觉,这样我就可以直接说:MyType a = new MyType(); MyType b = new MyType(); a = b;并执行一些自定义的赋值逻辑。 - Jon Galloway
为什么它不起作用的描述是非常准确的。正如你所建议的那样,Self属性的想法并不是很好。 - Charlie
在C++中,a = b会更改a的内容,但这并不完全准确。这取决于a和b是否为指针。a=b可能会使a指向b,也可能会调用您的=重载并将内容复制到a中。 - user585968
1
@MickyDuncan 嗯...这完全正确。如果a和b是指针,你认为它们的内容是什么?当内容是指针时,“仅复制指针”与“复制内容”有何不同? - The Dag
@TheDag 在C++中,赋值可以使指针指向相同的对象,也可以根据变量类型更改内容。我们并没有确定我们只是在谈论指针。 - user585968

3

在传递引用时,您可以使类成为不可变的,而不是复制数据。当类是不可变的时,拥有多个对它的引用并不是一个问题,因为它无法更改。

当更改数据的操作时,必须返回新的实例。


2

之前的帖子建议使用以下代码:

public static implicit operator Foo(string normalString) { }

我尝试了这种方法…但是要让它工作,你需要添加以下代码:

public static implicit operator Foo(Foo original) { }

而且编译器不允许你从自己的确切类型或任何基类型中拥有隐式转换函数。这是有道理的,因为这将是一种绕过赋值运算符的后门方式,而C#不想允许这种情况发生。


"implicit" 关键字用于声明隐式的用户定义类型转换运算符。使用它来启用用户定义类型与另一种类型之间的隐式转换,..." - MSDN - user585968

0

这是我自己使用的解决方案:

public class MyTestClass
{
   private int a;
   private string str;

   public MyTestClass()
   {
      a = 0;
      str = null;
   }

   public MyTestClass(int a, string str)
   {
      this.a = a;
      this.str = str;
   }

   public MyTestClass Clone
   {
      get
      {
         return new MyTestClass(this.a, this.str);
      }
   }
}

在代码的其他地方:

MyTestClass test1 = new MyTestClass(5, "Cat");
MyTestClass test2 = test1.Clone;

-4

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