Deep Object Copy

یه وقتهایی لازم میشه که ۲ تا Instance از ۲ تا کلاس مختلف که Property های شبیه به هم دارن رو به صورت یک طرفه Sync کنیم. در واقع میخوایم یکیشون رو توی اون یکی کپی کنیم.

برای بهتر روشن شدن موضوع یه مثال میزنم. فرض کنید که 4 تا کلاس داریم به این صورت:

Tester.DeepCopy.Models.Province
Tester.DeepCopy.Models.City
Tester.DeepCopy.ClumsyModels.ClumsyProvince
Tester.DeepCopy.ClumsyModels.ClumsyCity

کد داخل این کلاسها رو میتونین تصور کنین که چی خواهد بود:

Tester.DeepCopy.Models:

using System.Text;

namespace Tester.DeepCopy.Models {
	public class City {
		public long Id { get; set; }
		public long ProvinceId { get; set; }
		public string Title { get; set; }

		public Province Province { get; set; }

		public override string ToString() {
			StringBuilder sb = new StringBuilder();
			sb.Append("{");
			System.ComponentModel.PropertyDescriptorCollection pdcSource = 
				System.ComponentModel.TypeDescriptor.GetProperties(this.GetType());
			for (int i = 0; i < pdcSource.Count; i++) {
				System.ComponentModel.PropertyDescriptor pd = pdcSource[i];
				sb.AppendFormat(&amp;amp;quot;{0}={1}&amp;amp;quot;, pd.Name, pd.GetValue(this));
				if (i < pdcSource.Count - 1) sb.Append(&amp;amp;quot;, &amp;amp;quot;);
			}
			sb.Append(&amp;amp;quot;}&amp;amp;quot;);
			return sb.ToString();
		}
	}

	public class Province {
		public long Id { get; set; }
		public string Title { get; set; }

		public override string ToString() {
			StringBuilder sb = new StringBuilder();
			sb.Append(&amp;amp;quot;{&amp;amp;quot;);
			System.ComponentModel.PropertyDescriptorCollection pdcSource = 
				System.ComponentModel.TypeDescriptor.GetProperties(this.GetType());
			for (int i = 0; i < pdcSource.Count; i++) {
				System.ComponentModel.PropertyDescriptor pd = pdcSource[i];
				sb.AppendFormat(&amp;amp;quot;{0}={1}&amp;amp;quot;, pd.Name, pd.GetValue(this));
				if (i < pdcSource.Count - 1) sb.Append(&amp;amp;quot;, &amp;amp;quot;);
			}
			sb.Append(&amp;amp;quot;}&amp;amp;quot;);
			return sb.ToString();
		}
	}
}

Tester.DeepCopy.ClumsyModels:

using System.Text;

namespace Tester.DeepCopy.ClumsyModels {
	public class ClumsyCity {
		public string Title { get; set; }
		public long Id { get; set; }
		public ClumsyProvince Province { get; set; }
		public long ProvinceId { get; set; }

		public override string ToString() {
			StringBuilder sb = new StringBuilder();
			sb.Append(&amp;amp;quot;{&amp;amp;quot;);
			System.ComponentModel.PropertyDescriptorCollection pdcSource = 
				System.ComponentModel.TypeDescriptor.GetProperties(this.GetType());
			for (int i = 0; i < pdcSource.Count; i++) {
				System.ComponentModel.PropertyDescriptor pd = pdcSource[i];
				sb.AppendFormat(&amp;amp;quot;{0}={1}&amp;amp;quot;, pd.Name, pd.GetValue(this));
				if (i < pdcSource.Count - 1) sb.Append(&amp;amp;quot;, &amp;amp;quot;);
			}
			sb.Append(&amp;amp;quot;}&amp;amp;quot;);
			return sb.ToString();
		}
	}

	public class ClumsyProvince {
		public long Id { get; set; }
		public string Title { get; set; }

		public override string ToString() {
			StringBuilder sb = new StringBuilder();
			sb.Append(&amp;amp;quot;{&amp;amp;quot;);
			System.ComponentModel.PropertyDescriptorCollection pdcSource = System.ComponentModel.TypeDescriptor.GetProperties(this.GetType());
			for (int i = 0; i < pdcSource.Count; i++) {
				System.ComponentModel.PropertyDescriptor pd = pdcSource[i];
				sb.AppendFormat(&amp;amp;quot;{0}={1}&amp;amp;quot;, pd.Name, pd.GetValue(this));
				if (i < pdcSource.Count - 1) sb.Append(&amp;amp;quot;, &amp;amp;quot;);
			}
			sb.Append(&amp;amp;quot;}&amp;amp;quot;);
			return sb.ToString();
		}
	}
}

همونطور که میبینین، کد داخل کلاسها، شبیه به هم هست، ولی کلاسهای توی ۲ تا Namespace مجزا هستند و ترتیب Property ها یکسان نیست.

حالا موقعیتی رو در نظر بگیرید که یک Instance از کلاس City دارید و میخواین اون رو کپی کنین توی یه Instance از کلاس ClumsyCity. ابتدایی ترین راه اینه که Propertyهاش رو نظیر به نظیر برابر کنین با Property های City؛ ولی اگه تعداد Proprty ها زیاد بود چی؟ اگه فقط همین 2 تا کلاس نبود و مثلاً با 20 تا کلاس باید همین کار رو میکردین چطور!!؟

مثلاً موقعیتی رو در نظر بگیرید که توی یک Solution شما هم برنامه ی Web-Based دارید و  هم پروژه‌ی Smart Device. دیتابیسهای روی Deviceها رو میخواین Sync کنین با سرور اصلی. چه می کنین!!؟

اینجاست که DeepCopy که یه Extension Method خیلی ساده هست به درد میخوره:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;

namespace DNE.ExtensionMethods {
	/// <summary>
	/// My Extension Methods
	/// </summary>
	public static class Extensions {
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="target"></param>
		/// <param name="source"></param>
		public static void DeepCopy(this Object target, T source) {
			if (source == null) { target = null; return; }
			if (target == null) { throw new ArgumentNullException(&amp;amp;quot;target&amp;amp;quot;); }

			PropertyDescriptorCollection pdcSource = TypeDescriptor.GetProperties(source.GetType());
			PropertyDescriptorCollection pdcTarget = TypeDescriptor.GetProperties(target.GetType());

			pdcSource.Cast().ToList().ForEach(pdS => {
				PropertyDescriptor pdT = pdcTarget.Find(pdS.Name, true);
				if (pdT != null) {
					if (pdT.PropertyType == pdS.PropertyType) {
						pdT.SetValue(target, pdS.GetValue(source));
					} else {
						ConstructorInfo ciCtor = pdT.PropertyType.GetConstructor(Type.EmptyTypes);
						if (pdT.GetValue(target) == null) {
							pdT.SetValue(target, ciCtor.Invoke(null));
							pdT.GetValue(target).DeepCopy(pdS.GetValue(source));
						} else {
							pdT.GetValue(target).DeepCopy(pdS.GetValue(source));
						}
					}
				}
			});
		}
	}
}

طرز استفاده اش هم خیلی ساده است:

static void TestDeepCopy() {
	City c1 = new City {
		Id = 1,
		ProvinceId = 1,
		Title = "Tehran",
		Province = new Province { 
			Id = 1,
			Title = "Tehran"
		}
	};
	ClumsyCity c2 = new ClumsyCity();
	c2.DeepCopy(c1);

	Console.WriteLine("City: {0}", c1);
	Console.WriteLine("Clumsy City: {0}", c2);

}

نتیجه‌ی اجرای متد بالا اینطور میشود:

City: {Id=1, ProvinceId=1, Title=Tehran, Province={Id=1, Title=Tehran}}
Clumsy City: {Title=Tehran, Id=1, Province={Id=1, Title=Tehran}, ProvinceId=1}
Press any key to continue . . .

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>