[英]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
)。
Person
的Assign()
方法可能如下所示:
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反序列化。
如果您使用.Net本機序列化,那么您可以查看MSDN ISerializable
示例。 該示例確切地說ISerializable.GetObjectData
- 如何覆蓋ISerializable.GetObjectData
以在每次調用時返回相同的實例。
如果您正在使用Xml序列化( XmlSerializer
),那么您在對象的父類中手動實現IXmlSerializable
,然后每次都要注意獲取單個實例。
最簡單的方法是通過訪問某種靜態緩存來確保在父屬性的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);
}
}
}
然后你會得到一個帶有某種字典的靜態類,你可以快速檢索單例實例(如果它不存在則創建它)。
雖然這可能對你有用,但我也同意其他人認為這很少是最好(或唯一)的方法。 肯定有一種方法可以重構代碼,這樣就不需要了,但是你應該提供一些關於預期用途的附加信息,從哪里訪問這些數據,或者為什么類真正需要引用單個對象。
[編輯]
由於您使用類似單靜態緩存,您應該注意正確地實現它。 這意味着幾件事:
您的緩存類應該有一個私有構造函數。 您不希望任何人明確創建新實例 - 這在使用單例時不是一個選項。 因此,這意味着您應該公開一些公共靜態屬性,如Cache.Instance
,它將始終返回對同一私有對象的引用。 由於構造函數是私有的,因此您確定只有Cache類可以在初始化(或第一次更新)期間創建實例。 有關實現此模式的詳細信息, 請訪問http://www.yoda.arachsys.com/csharp/singleton.html (這也是一個很好的線程安全實現)。
當所有對象都具有相同的實例時,您可以簡單地通知緩存以更新單個私有實例(例如,從某處調用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.