مدتی پیش نیاز پیدا کردم تا فراخوانی متدهایی را 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