C#中引用类型的值赋值

时间:2009-06-30 19:59:21

标签: c# .net

按引用类型的值实现赋值的正确方法是什么?我想执行一项任务,但不会更改参考。

以下是我所说的:

void Main()
{
    A a1 = new A(1);
    A a2 = new A(2);
    a1 = a2; //WRONG: Changes reference
    a1.ValueAssign(a2); //This works, but is it the best way?
}

class A
{
    int i;

    public A(int i)
    {
        this.i = i;
    }

    public void ValueAssign(A a)
    {
        this.i = a.i;
    }
}

我应该使用某种约定吗?我觉得我不是第一个遇到这种情况的人。感谢。

编辑:

哇。我想我需要针对我面临的实际问题更多地调整我的问题。我得到了很多答案,不符合不改变参考的要求。克隆不是问题所在。问题在于分配克隆。

我有许多依赖于A的类 - 它们都共享对A类相同对象的引用。因此,每当一个类改变A时,它会反映在其他类中,对吧?这一切都很好,直到其中一个类试图这样做:

myA = new A();

实际上我没有做new A()但是我实际上正在从硬盘驱动器中检索A的序列化版本。但无论如何,这样做会导致myA收到新的参考。它不再与依赖于A的其他类共享相同的A.这是我试图解决的问题。我希望所有具有A实例的类都受上面代码行的影响。

我希望这能澄清我的问题。谢谢。

12 个答案:

答案 0 :(得分:6)

听起来你在谈论克隆。有些对象会支持这一点(通过ICloneable)但大多数都不支持。在许多情况下,它无论如何都没有意义 - 复制FileStream对象意味着什么? ICloneable通常被认为是一个糟糕的界面,部分原因是它没有指定克隆的深度。

最好尝试改变你的思维方式,这是不必要的。我的猜测是你是一个C ++程序员 - 并且不希望任何判断:不要试图编写C#,就像它是C ++一样。你最终会得到单一的C#,它可能效果不好,可能效率低下,而且对于C#开发人员来说可能是不直观的。

一种选择是尝试在可能的情况下使类型不可变 - 此时它不会重要是否存在副本,因为无论如何都无法更改对象。这是String采用的方法,并且效果很好。遗憾的是,框架中还没有不可变的集合(还)。

在您的情况下,您将拥有ValueAssign而不是WithValue方法,而{{1}}将返回 new 实例,只更改了值。 (不可否认,这是你案件中唯一可用的价值......)我意识到这种复制(除了即将改变的属性之外)与我所说的关于复制在C#中有点单一的内容相反,但它在阶级而不是外部机构决定何时复制。

我怀疑我并没有很好地解释这一点,但我的一般建议是围绕它进行设计而不是试图明确地复制到所有地方。

答案 1 :(得分:2)

我相信你应该使用结构而不是类,因为结构是按值而不是通过引用工作。

答案 2 :(得分:2)

对于你想做的事情,我认为A.ValueAssign(otherA)是最好的方式。

鉴于你想要有一个A的引用,确保引用不被破坏是关键。

你也不会在这里使用单身人士模式吗?

答案 3 :(得分:1)

一种方法是使用复制构造函数。如,

MyClass orig = ...; MyClass copy = new MyClass(orig);

复制MyClass元素的位置。根据类包含的引用类型数量,这可能涉及复制构造函数的递归使用。

答案 4 :(得分:1)

其他人建议克隆他们的答案,但这只是交易的一部分。您还希望使用(可能深度)克隆的结果来替换现有对象的内容。这是一个非常类似C ++的要求。

它在C#中并不经常出现,因此没有标准的方法名称或运算符意味着“用该对象的内容副本替换此对象的内容”。

它在C ++中经常出现的原因是因为需要跟踪所有权以便可以执行清理。如果您有会员:

std::vector<int> ints;

您的优势在于,当封闭对象被销毁时,它将被正确销毁。但是如果你想用新的向量替换它,你需要swap来提高效率。或者你也可以:

std::vector<int> *ints;

现在你可以轻松交换一个新的,但你必须记住先删除旧的,并在封闭类的析构函数中。

在C#中你不需要担心。有一种正确的方法:

List<int> ints = new List<int>();

您无需清理它,您可以通过引用交换新的。最好的。

修改

如果您有多个“客户端”对象需要保存对象的引用,并且您希望能够替换该对象,则可以使它们保持对充当“包装器”的中间对象的引用。

class Replaceable<T>
{
    public T Instance { get; set; }
}

其他类将保留对Replaceable<T>的引用。那么代码需要交换替代品。 e.g。

Replaceable<FileStream> _fileStream;

声明一个事件可能也很有用,因此客户可以订阅以找出更换存储的实例的时间。 Reusable version here

您还可以定义implicit conversion operators以删除一些语法噪音。

答案 5 :(得分:1)

我们遇到的情况与您所说的完全一致。我们有许多对象引用对象的特定实例,我们想要更改对象的实例,以便引用该现有实例的每个对象都可以看到更改。

我们遵循的模式几乎就是你所拥有的 - 只是名称不同:

    class A
    {
        int i;
        public A(int i)
        {
            this.i = i;
        }
        public void Copy(A source)
        {
            this.i = source.i;
        }
    }

答案 6 :(得分:1)

在几个基于WinForms的应用程序中,我需要类似的功能,在我的情况下允许数据输入表单处理对象的副本,只有当用户选择保存时,才会将信息复制到原始对象上变化。

为了完成这项工作,我从Delphi时代带来了一个想法 - Assign()方法。

基本上,我写了(好吧,好吧,生成)一个方法,它将属性(和列表内容等)从一个实例复制到另一个实例。这允许我编写这样的代码:

var person = PersonRespository.FindByName("Bevan");
...
var copy = new Person();
copy.Assign(person);
using (var form = new PersonDataEntryForm(copy))
{
    if (form.ShowAsModelessDialog() == MessageReturn.Save)
    {
        person.Assign(copy);
    }
}

在用户选择保存之前,对话框中所做的更改是私有的,然后更新公共变量(person)。

Assign()的{​​{1}}方法可能如下所示:

Person

顺便说一下,使用public void Assign(Person source) { Name = source.Name; Gender = source.Gender; Spouse = source.Spouse; Children.Clear(); Children.AddRange( source.Children); } 方法使复制构造函数几乎很容易编写:

Assign()

答案 7 :(得分:1)

我希望有一个“第二好的”答案选项,因为任何提到观察者的人都应该得到它。观察者模式可行,但在我看来,这是不必要的,是过度的。

如果多个对象需要维护对同一对象的引用(下面的“MyClass”),并且需要对引用的对象(“MyClass”)执行赋值,那么最简单的方法是处理它是为了创建一个ValueAssign函数,如下所示:

public class MyClass
{
    private int a;
    private int b;

    void ValueAssign(MyClass mo)
    {
        this.a = mo.a;
        this.b = mo.b;
    }
}

只有在分配时依赖对象需要其他操作时,才需要观察者。如果您只想维护参考,这种方法就足够了。这个例子和我在我的问题中提出的例子相同,但我觉得它更能强调我的意图。

感谢您的所有答案。我认真考虑了所有这些。

答案 8 :(得分:1)

我遇到了完全相同的问题。 我解决它的方法是将所有内容引用的对象放在另一个对象中,并让所有内容引用外部对象。然后你可以改变内部对象,一切都能够引用新的内部对象。

OuterObject.InerObject.stuff

答案 9 :(得分:0)

如果我做对了,你说的是正确的Singleton反序列化。

  1. 如果您使用.Net本机序列化,那么您可以查看MSDN ISerializable示例。该示例确切地说明了 - 如何覆盖ISerializable.GetObjectData以在每次调用时返回相同的实例。

  2. 如果您正在使用Xml序列化(XmlSerializer),那么您在对象的类中手动实现IXmlSerializable,然后注意获取单个每次都是实例。

  3. 最简单的方法是通过访问某种静态缓存来确保在父属性的setter中。 (我发现这很脏,但这是一种简单的方法)。

  4. 例如:

     public class ParentClass
     {
          private ReferencedClass _reference;
          public ReferencedClass Reference
          {
              get
              { 
                  return _reference;
              }
              set
              {
                  // don't assign the value, but consult the
                  // static dictionary to see if we already have
                  // the singleton
                  _reference = StaticCache.GetSingleton(value);
              }
          }
     }
    

    然后你会得到一个带有某种字典的静态类,你可以快速检索单例实例(如果它不存在则创建它)。

    虽然这可能对你有用,但我也同意其他人认为这很少是最好(或唯一)的方法。肯定有一种方法可以重构代码,这样就不需要了,但是你应该提供一些关于预期用途的附加信息,从哪里访问这些数据,或者为什么类真正需要引用单个对象。< / p>

    <强> [编辑]

    由于 使用类似于单身的静态缓存,因此您应该注意实施正确。这意味着几件事:

    1. 您的缓存类应该有一个私有构造函数。您不希望任何人明确创建新实例 - 这在使用单例时不是一个选项。所以这意味着你应该公开一些公共静态属性,比如Cache.Instance总是返回对同一个私有对象的引用。 由于构造函数是私有的,因此您确定只有Cache类可以在初始化(或第一次更新)期间创建实例。 有关实现此模式的详细信息,请参阅http://www.yoda.arachsys.com/csharp/singleton.html(这也是一个非常好的线程安全实现)。

    2. 当你所有的对象都有相同的实例时,你可以简单地通知缓存更新单个私有实例(例如从某个地方调用Cache.Update())。这样您就可以更新每个人都在使用的唯一实例。

    3. 但是从您的示例中仍然不清楚您是如何确切地通知您的客户数据已被更新。事件驱动机制可以更好地工作,因为它可以让您解密代码 - Singletons are evil

答案 10 :(得分:0)

不可能在c#中重载赋值运算符,就像在c / c ++中一样。然而,即使这是一个选项我会说你试图解决症状不是问题。您的问题是新引用的分配是破坏代码,为什么不将读取值分配给原始引用?并且如果你害怕其他人可能是新的并且分配使对象变成单例或类似物,以便在创建后它不能被改变但参考将保持不变

答案 11 :(得分:0)

如果你这样做会怎么样:

public class B
{
    B(int i) {A = new A(i);}
    public A A {get; set;}
}

...

void Main()
{
    B b1 = new B(1);
    A a2 = new A(2);
    b1.A = a2;
}

在整个程序中,只能通过B的实例访问对A的引用。当您重新分配bA时,您正在更改A的引用,但这并不重要,因为所有外部引用仍然指向B.这与原始解决方案有很多相似之处,但它允许您以任何方式更改A而无需更新其ValueAssign方法。