MethodCallSerializer یا Serialize کردن فراخوانی متد
نویسنده: یونس بقائی
تاریخ: ۱۳۹۵/۰۸/۰۷ ۱۸:۵۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
مدتی پیش نیاز پیدا کردم تا فراخوانی متدهایی را Serialize کرده و در مواقعی خاص، آن متدها را فراخوانی کنم که نتیجهی آن را در زیر با هم میبینیم.
در نظر بگیرید متدی داریم به شکل زیر:
public class EmailSender
{
public void Send(string emailAddress)
{
Console.WriteLine($"an email was sent to {emailAddress}");
}
} و میخواهیم نحوه فراخوانی این متد را Serialize کرده
new EmailSender().Send("eng.younos1986@gmail.com") یعنی با چنین کدی، متد مورد نظر را Serialize کرده، Type ، Method.Name و Argument های آن را بدست آورده و در دیتابیس ذخیره کنیم:
Serialize(() => new EmailSender().Send("eng.younos1986@gmail.com")); public void Serialize(Expression<Action> methodCall)
{
var callExpression = methodCall.Body as MethodCallExpression;
var rawArguments = new object[callExpression.Arguments.Count];
var i = 0;
foreach (var argg in callExpression.Arguments)
{
rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
}
string typeName = string.Empty;
var serializedArgumentsObject = rawArguments.ObjectToByteArray(); //todo: save this to db as method parameters [Binary field]
if (callExpression.Object != null) //todo: save this to db as class name to be instanciated later {nvarchar field}
typeName = callExpression.Object.Type.ToString(); // instance methods
else
typeName = callExpression.Method.ReflectedType.ToString(); // static methods
var methodname = callExpression.Method.Name; //todo: save this to db as method name to be called via reflection [nvarchar field]
var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject(); //todo: retrieve serializedObject fro db and deserialize
var objInstance = GetInstance(typeName); //todo: retrieve typeName fro db and deserialize
if (objInstance != null)
{
objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject);
}
}
برای اینکه بتوانیم نحوه فراخوانی متد را به صورت Lambda Expressions به متد Serialize بفرستیم، باید نوع پارارمتر آن از جنس <Expression<Action باشد.
بعد با Cast کردن methodCall.Body به MethodCallExpression میتوانیم آرگیومنتها، نام متد و Type آن را بدست بیاوریم:
در خطوط 5 تا 10 کد بالا، آرایهای به طول تعداد Argument ها ساخته و هر Argument را به نوع خودش تبدیل کرده و درون آرایه میریزیم.
var rawArguments = new object[callExpression.Arguments.Count];
var i = 0;
foreach (var argg in callExpression.Arguments)
{
rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
} و با این کد
Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
هر آرگیومنت را به نوع خودش تبدیل کرده سپس Lambdaی آن را ساخته و کامپایل کرده تا Expression Tree به delegate تبدیل شده ( به کد معادل IL تبدیل شده)، سپس آن را با متد DynamicInvoke اجرا کرده تا دقیقا معادل آرگیومنت ارسالی را بدست آورده و در آرایه ذخیره میکنیم. در آخر آرایه بدست آمده را به بایت تبدیل کرده و میتوان در یک منبع داده دائمی ذخیره کرد.
با کد زیر، Typeی که متد، درون آن قرار دارد را بدست میآوریم:
if (callExpression.Object != null)
typeName = callExpression.Object.Type.ToString();
else
typeName = callExpression.Method.ReflectedType.ToString();
و همچنین نام متد:
var methodname = callExpression.Method.Name;
تا به اینجای کار Type، نام متد و آرگیومنتهای Serialize شده آن بدست آمدند که میتوان آنها را در دیتابیس ذخیره کرد. سپس استخراج، DeSerialize و فراخوانی کرد:
نحوه فراخوانی:
var methodname = callExpression.Method.Name;
var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject();
var objInstance = GetInstance(typeName);
if (objInstance != null)
{
objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject);
} و برای instance ساختن از Type مورد نظر از کد زیر استفاده میکنیم:
public object GetInstance(string strFullyQualifiedName)
{
Type type = Type.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
type = asm.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
}
return null;
}
موارد استفاده: نوشتن نرم افزارهایی برای مدیریت یک سری Job
کد کامل MethodCallSerialization.rar