دریافت نام Property با استفاده از عبارت های Lambda در #C

دوشنبه 6 اردیبهشت 1395

در این مقاله توضیح می دهیم که چگونه می توان Property ها را به عنوان پارامترهای متد از طریق عبارت های Lambda ارسال کرد و ابزاری را برای دریافت نام Property ها از طریق عبارت های Lambda ایجاد می کنیم. به عبارت دیگر نام Property ها را با استفاده از عبارت های Lambda در #C دریافت کنیم.

دریافت نام Property با استفاده از عبارت های Lambda در #C

شما می توانید Property ها را به عنوان پارامترهای متد ارسال کنید. این کار به ویژه برای اهداف پیکربندی مناسب است. در مثال آخر ضمیمه شده به این مقاله، با ابزار PropertiesAssert  عمل refactor  را انجام داده ایم بنابراین شما می توانید Property هایی را به صورت  lambda expression به جای متن خام تنظیم کنید.

چگونه می توانیم Property را به عنوان پارامتر Lambda  ارسال کنیم؟

در ابتدا باید کدی بنویسید که Property ها را به عنوان پارامتر ارسال کند. این کمی ترسناک به نظر می رسد اما نگران نباشید:

public static string GetMemberName<T>(Expression<Func<T, object>> expression)
{
    return GetMemberName(expression.Body);
}

از طریق  نوع عبارت <<Func<T, object> می توانید عبارت های lambda را برای Property ارسال کنید. T نوعی از کلاس است که Propery را نگه می دارد.

مرحله بعدی فرایند ایجاد یک متد برای دریافت نام Property از عبارت Lambda  است.

ساخت Lambda Expressions Reader

شما نیاز دارید که دو متد برای یک عمل ایجاد کنید. متد اصلی شامل یک منطق برای دریافت نام Property می باشد، و متد دوم از نوع Void یا متد از نوع مقدار می باشد. اگر شما عبارت خالی یا عبارت های پشتیبانی نشده ارسال کنید یک استثنا به صورت ArgumentException رخ می دهد:

private static string GetMemberName(Expression expression)
{
    if (expression == null)
    {
        throw new ArgumentException(expressionCannotBeNullMessage);
    }

    if (expression is MemberExpression)
    {
        // Reference type property or field
        var memberExpression = (MemberExpression) expression;
        return memberExpression.Member.Name;
    }

    if (expression is MethodCallExpression)
    {
        // Reference type method
        var methodCallExpression = (MethodCallExpression) expression;
        return methodCallExpression.Method.Name;
    }

    if (expression is UnaryExpression)
    {
        // Property, field of method returning value type
        var unaryExpression = (UnaryExpression) expression;
        return GetMemberName(unaryExpression);
    }

    throw new ArgumentException(invalidExpressionMessage);
}

private static string GetMemberName(UnaryExpression unaryExpression)
{
    if (unaryExpression.Operand is MethodCallExpression)
    {
        var methodExpression = (MethodCallExpression) unaryExpression.Operand;
        return methodExpression.Method.Name;
    }

    return ((MemberExpression) unaryExpression.Operand).Member.Name;
}

 اگر این متدها به صورت extension method ایجاد شوند برای استفاده راحتتر هستند . همچنین ما یک متد اضافه کرده ایم که چندین عبارت lambda را می پذیرد. این کار از طریق عملگر param پیاده سازی شده است. برای ارسال یک متد به عنوان lambda expression از نوع <<Expression<Action<T برای پارامتر متد استفاده کنید:

public static class NameReaderExtensions
{
    private static readonly string expressionCannotBeNullMessage = "The expression cannot be null.";
    private static readonly string invalidExpressionMessage = "Invalid expression.";

    public static string GetMemberName<T>
    (this T instance, Expression<Func<T, object>> expression)
    {
        return GetMemberName(expression.Body);
    }

    public static List<string> GetMemberNames<T>
    (this T instance, params Expression<Func<T, object>>[] expressions)
    {
        List<string> memberNames = new List<string>();
        foreach (var cExpression in expressions)
        {
            memberNames.Add(GetMemberName(cExpression.Body));
        }

        return memberNames;
    }

    public static string GetMemberName<T>
    (this T instance, Expression<Action<T>> expression)
    {
        return GetMemberName(expression.Body);
    }

    private static string GetMemberName(Expression expression)
    {
        if (expression == null)
        {
            throw new ArgumentException(expressionCannotBeNullMessage);
        }

        if (expression is MemberExpression)
        {
            // Reference type property or field
            var memberExpression = (MemberExpression) expression;
            return memberExpression.Member.Name;
        }

        if (expression is MethodCallExpression)
        {
            // Reference type method
            var methodCallExpression = (MethodCallExpression) expression;
            return methodCallExpression.Method.Name;
        }

        if (expression is UnaryExpression)
        {
            // Property, field of method returning value type
            var unaryExpression = (UnaryExpression) expression;
            return GetMemberName(unaryExpression);
        }

        throw new ArgumentException(invalidExpressionMessage);
    }

    private static string GetMemberName(UnaryExpression unaryExpression)
    {
        if (unaryExpression.Operand is MethodCallExpression)
        {
            var methodExpression = (MethodCallExpression) unaryExpression.Operand;
            return methodExpression.Method.Name;
        }

        return ((MemberExpression) unaryExpression.Operand).Member.Name;
    }
}

تست راه اندازی ویژگی NameExtensions

Client client = new Client();
var propertyNames = client.GetMemberNames
(c => c.FistName, c => c.LastName, c => c.City);
foreach (var cPropertyName in propertyNames)
{
    Console.WriteLine(cPropertyName);
}
string nameOfTheMethod = client.GetMemberName(c => c.ToString());
Console.WriteLine(nameOfTheMethod);

همانطور که انتظار می رود کنسول FirstName، LastName، City و ToString را نمایش داده است:

بهبود تنظیمات PropertiesAsserter با استفاده از عبارات Lambda

به طور خلاصه هدف اصلی PropertiesAsserter برای بدست آوردن تمام Property ها از یک آبجکت در برابر نسخه مورد انتظار می باشد. با این حال شما باید قادر به تنظیم آن برای پرش از برخی Property ها باشید شما ممکن است ندانید که چه مقداری مانند Property ها، فیلدهای History و غیره مورد انتظار است.

در اولین پیاده سازی این ویژگی پرش از Property ها بدست نمی آید، نام آنها به صورت string ارسال می شود:

public class ObjectToAssertValidator : PropertiesValidator<ObjectToAssertValidator, ObjectToAssert>
{
    public void Validate(ObjectToAssert expected, ObjectToAssert actual)
    {
        this.Validate(expected, actual, "FirstName");
    }
}

همانطور که می دانید روش بعدی error-prompt می باشد زیرا امکان خطای تایپی و باقی ماندن Property ها وجود دارد.

متد اصلی از PropertiesAsserter می تواند برای پذیرفتن پارامترهای Property های عبارات Lambda تغییر کند. به طور داخلی این متد از ویژگی lambda expressions جدید برای بدست آوردن نام های Property ها استفاده خواهد کرد:

public void Assert<T>(T expectedObject, T realObject, 
params Expression<Func<T, object>>[] propertiesNotToCompareExpressions)
{
    PropertyInfo[] properties = realObject.GetType().GetProperties();
    List<string> propertiesNotToCompare = 
		expectedObject.GetMemberNames(propertiesNotToCompareExpressions);
    foreach (PropertyInfo currentRealProperty in properties)
    {
        if (!propertiesNotToCompare.Contains(currentRealProperty.Name))
        {
            PropertyInfo currentExpectedProperty = 
		expectedObject.GetType().GetProperty(currentRealProperty.Name);
            string exceptionMessage =
                string.Format("The property {0} of class {1} was not as expected.", 
                currentRealProperty.Name, currentRealProperty.DeclaringType.Name);

            if (currentRealProperty.PropertyType != typeof(DateTime) && 
            currentRealProperty.PropertyType != typeof(DateTime?))
            {
                MSU.Assert.AreEqual(currentExpectedProperty.GetValue(expectedObject, null), 
                currentRealProperty.GetValue(realObject, null), exceptionMessage);
            }
            else
            {
                DateTimeAssert.AreEqual(
                    currentExpectedProperty.GetValue(expectedObject, null) as DateTime?,
                    currentRealProperty.GetValue(realObject, null) as DateTime?,
                    DateTimeDeltaType.Minutes,
                    5);
            }
        }
    }
}

در اینجا کد بهبود یافته تنظیمات وجود دارد:

public class ObjectToAssertAsserter : PropertiesAsserter<ObjectToAssertAsserter, ObjectToAssert>
{
    public void Assert(ObjectToAssert expected, ObjectToAssert actual)
    {
        this.Assert(expected,
                    actual,
                    e => e.LastName,
                    e => e.FirstName, 
                    e => e.PoNumber);
    }
}

آموزش سی شارپ

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

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

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

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

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