任意のオブジェクトをディープ コピーする気の利いたメソッドを作成しました。これはMemberwiseClone()
、インスタンス内の参照型フィールドを再帰的に呼び出すことによって行われます。このメソッドは、階層関係オブジェクトを含め、私が使用したいすべてのオブジェクトで完全に機能します。このメソッドは、過去の訪問の辞書も備えているため、不要な重複作業が回避されます。
ただし、私が抱えている問題は、クローンが必要なときにオブジェクトが WPF/MVVM にデータ バインドされていない場合にのみ、この方法が機能することです。INotifyPropertyChanged.PropertyChanged
データがバインドされ、メソッドが呼び出されると、イベントと WPF フレームワークの間に確立されたリンクが原因で (私は推測します)、スタック オーバーフロー例外が発生します。次に、再帰呼び出しは、AppDomain
および低レベルの Pointer オブジェクトを含むオブジェクトのユニバース全体をコピーしようとします。これらのオブジェクトは、リンクされているように見え、ほぼ無限に続きます (とにかく、VS2012 が処理できる範囲を超えています)。
オブジェクトグラフの先頭に戻るディープコピーが必要になることはないと思いますAppDomain
...特定の境界に達したときにコピーメソッドを「停止」させるスマートな方法はありますか? また、オブジェクトをデータにバインドする前に単純にコピーすることも考えましたが、それが実行可能なオプションかどうかはわかりませんし、かなりばかげています。シリアル化できないタイプで動作するシンプルなディープ コピー ソリューションが必要なだけですが、INotifyPropertyChanged
.
メソッドの実装:
private static object Clone(object instance, IDictionary<object, object> visitGraph)
{
var instanceType = instance.GetType();
Debug.WriteLine(instanceType.Name);
object clonedInstance = null;
if (visitGraph.ContainsKey(instance))
{
clonedInstance = visitGraph[instance];
}
else
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance;
var memberwiseCloneMethod =
instanceType.GetMethods(flags).Single(x => x.Name == "MemberwiseClone" &&
!x.GetParameters().Any());
clonedInstance = memberwiseCloneMethod.Invoke(instance, null);
visitGraph.Add(instance, clonedInstance);
var allReferenceTypeProperties = clonedInstance.GetType().GetAllFields()
.Where(
x =>
!x.FieldType.IsValueType
&& x.FieldType != typeof (string));
foreach (var field in allReferenceTypeProperties)
{
var existingFieldValue = field.GetValue(instance);
if (existingFieldValue != null)
{
var clonedFieldValue = Clone(existingFieldValue, visitGraph);
field.SetValue(clonedInstance, clonedFieldValue);
}
}
}
return clonedInstance;
}
public static IEnumerable<FieldInfo> GetAllFields(this Type type)
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance;
var fields = type.GetFields(flags);
foreach (var field in fields)
{
yield return field;
}
if (type.BaseType != null)
{
foreach (var field in GetAllFields(type.BaseType))
{
yield return field;
}
}
}
public static object Copy(this object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
var visitGraph = new Dictionary<object, object>();
var clonedInstance = Clone(instance, visitGraph);
return clonedInstance;
}