C# Simplify property selector in constructor with Generic Type

Viewed 640

I'd like to simplify some code, if possible.

Current Constructor (T is within the scope, defined in the outer type)

public Column(string propertyName)
{
    PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);

    _ = propertyInfo ?? throw new ArgumentException(message: $"Property {propertyName} does not exist on {typeof(T).Name}");

    ...
}

I want to know if it is possible to make property a Lambda expression or something to select the property of Generic Type T.

This of course is to make our development easier with fewer mistakes.

Current use

new DataTable<someClass>.Column(nameof(someClass.someProperty))

I would like to do something like:

new DataTable<someClass>.Column(someClass.someProperty) (without declaring a new someClass)

OR

new DataTable<someClass>.Column(t = > t.someProperty)

2 Answers

You can use the below method to extract property name from Expression

    public static PropertyInfo GetAccessedMemberInfo<T>(this Expression<T> expression)
    {
        MemberExpression? memberExpression = null;

        if (expression.Body.NodeType == ExpressionType.Convert)
        {
            memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression;
        }
        else if (expression.Body.NodeType == ExpressionType.MemberAccess)
        {
            memberExpression = expression.Body as MemberExpression;
        }

        if (memberExpression == null)
        {
            throw new ArgumentException("Not a member access", "expression");
        }

        return memberExpression.Member as PropertyInfo ?? throw new Exception();
    }

Then use it like this

public Column(Expression<Func<T, object>> prop)
{
    PropertyInfo propertyInfo = prop.GetAccessedMemberInfo();
}

new DataTable<someClass>.Column(t = > t.someProperty)

Previous answer is more complete and support more scenarios, but also more complex.

If you don't need such flexibility, this enforces you to strongly type and also makes sure that the constructor never throws

namespace ConsoleApp1
{

    public class Column<T, TProperty>
    {
        Func<T, TProperty> functionToBeApplied;

        // Pass a function, it can never throw
        public Column(Func<T, TProperty> functionToBeApplied)
        {
            this.functionToBeApplied = functionToBeApplied;
        }

        // Apply the function to the object
        public string GetPropertyAsString(T obj)
        {
            TProperty property = functionToBeApplied(obj);
            return property.ToString();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            var column = new Column<string, int>(x => x.Length);

            Console.WriteLine($"Size of string is {column.GetPropertyAsString("this is my object")}");
        }
    }
}
Related