Deep Copy سریع با استفاده از Expression Trees در #C
یکشنبه 28 شهریور 1395در این مقاله ما به شما آموزش میدهیم که چگنه با استفاده از Expression Trees تابع deep copy را با سرعت بیشتری اجرا و پیاده سازی کنیم.
Deep Copy(کپی عمیق) در #C پیاده سازی نشده است ، فقط تابع ()Object.MemberwiseClone روی هر object موجود است که فقط کپی های کم عمق(shallow copy) را میسازد.
به طور کلی تابع شبیه سازی عمیق ( deep cloning function) میتواند به راحتی توسط Serialization ، با تلاشی متوسط با Reflection و با تلاشی زیاد با Expression Trees پیاده سازی میشود.
Serialization خیلی کند است، و ممکن است محدودیت هایی برای ما ایجاد کند.
Reflection پنج مرتبه سریعتر است اما هنوز خیلی کند تر از پیاده سازی دستی تابع کپی است.
Expression Trees سرعتی برابر بلکه سریعتر از پیاده سازی دستی تابع کپی دارد.
مقایسه سرعت :
مقایسه سرعت با ذکر کد های Serialization و Reflection از قسمت "Links to Alternatives" انجام شده است.
object استفاده شده : ModerateClass
(در فایل zip موجود است).
جدول با رقم های دقیق :
چرا Expression Trees خیلی سریع هستند؟
Expression Trees یک توسعه دهنده در زمان اجرا را قادر میسازد بتواند متدها و تابع های کلی را بسازد که این به Reflection بسیار نزدیک است.
فرق Reflection این است که شما مجبور به نوشتن کد بسیار طولانی هستید و همچنین اینکه در Expression Trees متد شما قبل از استفاده نیاز به compile دارد.
compile کردن در زمان اجرا مدت زمانی را از ما میگیرد.اما اگر در برنامه آن متد دوباره در زمان اجرا مورد استفاده قرار بگیرد سرعت compile کردن آن با سرعت compiled کردن source کد برابر است.
مزایا و محدودیت ها :
مزایاها در زیر لیست شده اند:
• عضو های private و public را کپی میکند.
• برای فیلد های فقط خواندنی (readonly ) و propertie ها کار میکند.
• برای کلاس ها و structs ها کار میکند.
• برای مراجع غیر مستقیم (circular references) کار میکند.
• Object ها با چند reference برای یک بار کپی میشوند.
• انواع ارث برده (inherited types) را کپی میکند.
• انواع generic را کپی میکند.
• با multithread ها به خوبی کار میکند.
• با کلاس هایی که سازنده شان پارامتر ندارد به خوبی کار میکند.
• کلاس ها نیاز به صفت Serializable
ندارند.
لیست محدودیت ها :
• با NET 4. به بالا کارمیکند.
• delegate و event ها را کپی نمیکند.
• ComObjects
را پشتیبانی نمیکند
• object های مدیریت نشده را پشتیبانی نمیکند.
چگونه از کد استفاده کنیم؟
فایل DeepCopyByExpressionTrees.cs را در پروژه خود کپی کنید.
سپس شما میتوانید از deep copy به عنوان یک extension method روی هر object استفاده کنید.
;()var copy = original.DeepCopyByExpressionTree
یا اینکه به وسیله پیاده سازی DeepCopy
به روش مستقیم بر روی object :
class ExampleClass } public ExampleClass DeepCopy() { return this.DeepCopyByExpressionTree(); } }
متد اصلی و دیکشنری مرجع(references dictionary ) :
متد T DeepCopyByExpressionTree<T>(this T original,...)
که public static generic است متد private و غیر جنریک object DeepCopyByExpressionTreeObj(object original,...) رافرخوانی میکندو بهreferences dictionary تازه ساخته شده از نوع Dictionary<object, object> ارسال میکند.
References dictionary کلاس های اورجینال و کپی های مربوط به خودشان را ذخیره میکند.آن ها دوباره بر پایه ReferenceEquals
مورد استفاده قرار میگیرند و مقایسه انجام میدهد بنابراین ما از چند کپی در یک object یکسان دوری میکنیم.
ساختن و ذخیره کردن متد های Lambda :
برای هر نوع از کلاس ما یک متد پویای deep copy مخصوص تولید کرده ایم.این به وسیله متد (GetOrCreateCompiledLambdaCopyFunction(Type type ساخته شده است. که thread امن ساخته شده و تابع های کامپایل شده را برای دیکشنری ایستای Dictionary<Type, Func<...>> CompiledCopyFunctionsDictionary
ذخیره میکند.
گردش کار(Workflow ) از توابع کپی:
نوع خاص تابع های lambda deep Copy با دستور کار زیر ساخته شده اند :
1-object در اول توسط تابع MemberwiseClone
کپی میشود.
2_ کلاس های کپی، در references dictionary تحت کلید اورجینال object ذخیره شده اند.
3- فیلد های need-deep-copy به وسیله Reflection بدست آمده اند و یکی یکی از طریق DeepCopyByExpressionTreeObj کپی شده اند.، به این معنی که متد لامبدا (lambda ) برای شی Obj1
فراخوانی میکند متد لامبدا برای فرزند خودش Obj1.Obj2 کامپایل میکند.
4- delegate ها و event ها null ست شده اند.
5- برای آرایه های کلاس یا interfaces، تابع کپی برای هر ایندکس فراخوانی و حلقه پویا for میسازد.
FieldInfo های need-deep-copy توسط reflection فقط یک بار تهیه میشوند: قبل از کامپایل شدن تابع کپی و از کند شدن روند کامپایل تابع جلوگیری میکند.
نکته(Need-deep-copy fields ) :
این فیلد ها یا از نوع بی مقدار هستند یا از نوع غیر اصلی.
مخصوصا وقتی که نوع های string , enum و decimal حذف شده باشند.
با این ما تمام کلاس های دیگر، string ها و struct های تایپ های پایه را دریافت میکنیم.
علاوه بر این، از deep-copy آنها حذف میشوند همچنین انواع struct که شامل هیچ کلاسی یا interface نیستند نیز حذف میشوند.
نکته (Properties) : ما اصلا مراقب property ها نیستیم. هر propertiyبه صورت اتوماتیک تولید (generate) میشوند.
نکته (فایل های Readonly ) : فایل های فقط خواندنی تنها چیزایی هستند که به صورت آهسته کپی میشوند.زیرا expression trees اینجا کار نمیکند.
چگونه Expression Trees شرکت میکنند؟
به وسیله Expression Trees، کد های regular پویا را تقلید میکنیم . ما روند را با ارائه یک مثال ساده نشان خواهیم داد.(MemberwiseClone
)
در regular code به راحتی ما یک خط مینویسیم.
ExampleClass output = (ExampleClass)input.MemberwiseClone();
اما کدها ممکن است تنها یک بار در کنار exampleClass تازمانیکه MemberwiseClone محافظت میکند استفاده میشود.
با استفاده از Reflection :
MethodInfo MemberwiseCloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); ExampleClass output = (ExampleClass)MemberwiseCloneMethod.Invoke(input, null);
که ممکن است خارج از کلاس صدا زده شوند.
با استفاده از Expression Trees
ParameterExpression inputParameter = Expression.Parameter(typeof(ExampleClass)); ParameterExpression outputParameter = Expression.Parameter(typeof(ExampleClass)); MethodInfo MemberwiseCloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); Expression lambdaExpression = Expression.Assign( outputParameter, Expression.Convert( Expression.Call( inputParameter, MemberwiseCloneMethod), typeof(ExampleClass)));
نکته های جالب (پوشه های readonly و Structها) :
بدون در دست داشتن deep copy از struct ها، عملیات deep copy برای کلاس های پایه مانند Dictionary<,> کار نخواهد کرد زیرا دیکشنری ها جفت کلید-مقدار درون struct هایی از نوع Dictionary<,> ذخیره می کنند.
Entry و این Entrystruct ها deep-copie دارند.
ما نیاز به پیاده سازی struct copy داریم.
struct ها ها ممکن است نوشتنی یا فقط خواندنی باشند. کپی کردن فایل های نوشتنی به وسیله expression tree باید مستقیما روی نوع های struct انجام شود(بدون cast).اما برای کپی کردن فایل های readOnly در structما باید از reflection استفاده کنیم و همچنین باید از boxing استفاده شود.
مانند کد های زیر:
// definition of variables ExampleStruct copy = (ExampleStruct)original.MemberwiseClone(); Object boxedCopy = (Object)copy; FieldInfo fieldInfo = copy.GetType().GetField("field"); // WRITABLE fields in structs (we use pure expression trees) copy.field = value; // WORKING ((ExampleStruct)boxedCopy).field = value // NOT WORKING: we assigned field of temporary struct instance fieldInfo.SetValue(boxedCopy, value); // WORKING BUT SLOW (if repeated many times) // READONLY fields in structs (we have to use reflection call in expression trees) copy.field = value; // NOT WORKING: because of readonly fieldInfo.SetValue(copy, value); // NOT WORKING: copy was expected of type System.Object fieldInfo.SetValue((Object)copy, value); // NOT WORKING: value is again lost because of unwanted boxing fieldInfo.SetValue(boxedCopy, value); // WORKING
- C#.net
- 3k بازدید
- 1 تشکر