Deep Copy سریع با استفاده از Expression Trees در #C

یکشنبه 28 شهریور 1395

در این مقاله ما به شما آموزش میدهیم که چگنه با استفاده از Expression Trees تابع deep copy را با سرعت بیشتری اجرا و پیاده سازی کنیم.

Deep Copy سریع با استفاده از Expression Trees در #C

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

آموزش سی شارپ

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 2k بازدید
  • 1 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید