簡體   English   中英

C#中引用類型的值賦值

[英]Value assignment for reference type in C#

按引用類型的值實現賦值的正確方法是什么? 我想執行一項任務,但不更改參考。

這就是我在說的:

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實例的類都受上面代碼行的影響。

我希望這能澄清我的問題。 謝謝。

聽起來你在談論克隆。 有些對象會支持這種情況(通過ICloneable )但大多數都不支持。 在許多情況下,它無論如何都沒有意義 - 復制FileStream對象意味着什么? ICloneable通常被認為是一個壞的接口,部分原因是它沒有指定克隆的深度。

最好嘗試改變你的思維方式,這是不必要的。 我的猜測是,你是一個C ++程序員-但不期望施放任何所有的判斷:不要試圖寫C#,如果是C ++。 你最終會得到單一的C#,它可能效果不好,可能效率低下,而且對於C#開發人員來說可能是不直觀的。

一種選擇是嘗試在可能的情況下使類型不可變 - 此時無論是否存在副本都無關緊要 ,因為無論如何都無法更改對象。 這是String采用的方法,並且它非常有效。 遺憾的是,框架中還沒有不可變的集合(尚未)。

在您的情況下,您將擁有WithValue而不是使用ValueAssign方法,它將返回僅具有更改值的實例。 (不可否認,這是你案件中唯一可用的價值......)我意識到這種復制(除了即將改變的屬性之外)與我所說的關於復制在C#中有點單一的內容相反,但它在階級而不是外部機構決定何時復制。

我懷疑我並沒有很好地解釋這一點,但我的一般建議是圍繞它進行設計,而不是試圖明確地復制到所有地方。

我相信你應該使用結構而不是類,因為結構是按值而不是通過引用工作的。

對於你想做的事情,我認為A.ValueAssign(otherA)是最好的方式。

假設你想要一個A的引用,確保引用不被破壞是關鍵。

你也不會在這里使用單身人士模式嗎?

一種方法是使用復制構造函數。 例如,

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

在哪里復制MyClass的元素。 根據類包含的引用類型數量,這可能涉及復制構造函數的遞歸使用。

其他人建議克隆他們的答案,但這只是交易的一部分。 您還希望使用(可能深度)克隆的結果來替換現有對象的內容。 這是一個非常類似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>的引用。 那么代碼需要交換替代品。 例如

Replaceable<FileStream> _fileStream;

聲明一個事件可能也很有用,因此客戶可以訂閱以找出更換存儲的實例的時間。 可重復使用的版本

您還可以定義隱式轉換運算符以刪除一些語法噪音。

我們的情況是,我們完全按照您的意思行事。 我們有許多對象引用對象的特定實例,我們想要更改對象的實例,以便引用該現有實例的每個對象都可以看到更改。

我們遵循的模式幾乎就是你所擁有的 - 只是名稱不同:

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

在幾個基於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 )。

PersonAssign()方法可能如下所示:

public void Assign(Person source)
{
    Name = source.Name;
    Gender = source.Gender;
    Spouse = source.Spouse;

    Children.Clear();
    Children.AddRange( source.Children);
}

順便說一下,使用Assign()方法使復制構造函數幾乎很容易編寫:

public Person(Person original)
    : this()
{
    Assign(original);
}

我希望有一個“第二好的”答案選項,因為任何提到觀察者的人都應該得到它。 觀察者模式可行,但在我看來,這是不必要的,是過度的。

如果多個對象需要維護對同一對象的引用(下面的“MyClass”)並且您需要對引用的對象(“MyClass”)執行賦值 ,則處理它的最簡單方法是創建ValueAssign函數,如下所示:

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

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

只有在分配時依賴對象需要其他操作時,才需要觀察者。 如果您只想維護參考,這種方法就足夠了。 這個例子和我在我的問題中提出的例子相同,但我覺得它更能強調我的意圖。

謝謝你的所有答案。 我認真考慮了所有這些。

我有同樣的問題。 我解決它的方法是將所有內容引用的對象放在另一個對象中,並讓所有內容引用外部對象。 然后你可以改變內部對象,一切都能夠引用新的內部對象。

OuterObject.InerObject.stuff

如果我做對了,你說的是正確的Singleton反序列化。

  1. 如果您使用.Net本機序列化,那么您可以查看MSDN ISerializable示例。 該示例確切地說ISerializable.GetObjectData - 如何覆蓋ISerializable.GetObjectData以在每次調用時返回相同的實例。

  2. 如果您正在使用Xml序列化( XmlSerializer ),那么您在對象的類中手動實現IXmlSerializable ,然后每次都要注意獲取單個實例。

  3. 最簡單的方法是通過訪問某種靜態緩存來確保在父屬性的setter中。 (我發現這很臟,但這是一種簡單的方法)。

例如:

 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);
          }
      }
 }

然后你會得到一個帶有某種字典的靜態類,你可以快速檢索單例實例(如果它不存在則創建它)。

雖然這可能對你有用,但我也同意其他人認為這很少是最好(或唯一)的方法。 肯定有一種方法可以重構代碼,這樣就不需要了,但是你應該提供一些關於預期用途的附加信息,從哪里訪問這些數據,或者為什么類真正需要引用單個對象。

[編輯]

由於使用類似單靜態緩存,您應該注意正確地實現它。 這意味着幾件事:

  1. 您的緩存類應該有一個私有構造函數。 您不希望任何人明確創建新實例 - 這在使用單例時不是一個選項。 因此,這意味着您應該公開一些公共靜態屬性,如Cache.Instance ,它將始終返回對同一私有對象的引用。 由於構造函數是私有的,因此您確定只有Cache類可以在初始化(或第一次更新)期間創建實例。 有關實現此模式的詳細信息, 請訪問http://www.yoda.arachsys.com/csharp/singleton.html (這也是一個很好的線程安全實現)。

  2. 當所有對象都具有相同的實例時,您可以簡單地通知緩存以更新單個私有實例(例如,從某處調用Cache.Update() )。 這樣您就可以更新每個人都在使用的唯一實例。

但是,從您的示例中仍然不清楚您是如何確切地通知您的客戶數據已更新。 事件驅動機制可以更好地工作,因為它可以讓你解密你的代碼 - 單身人士是邪惡的

在c#中重載賦值運算符是不可能的,就像在c / c ++中一樣。 然而,即使這是一個選項我會說你試圖解決症狀不是問題。 您的問題是新引用的分配是破壞代碼,為什么不將讀取值分配給原始引用? 如果你害怕其他人可能是新的並且分配使對象變成單例或類似物,以便在創建后它不能被改變但參考將保持不變

如果你這樣做會怎么樣

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方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM