Justin Adkins
Justin Adkins

Reputation: 1234

How to get the value of lambda expression

I'm pretty stumped with this one.

I'm developing an HTML Extension method that accepts an int value in the form of a lambda expression.

I've tried to emulate it the best I could below in a simple c# application. I'm having trouble calling my extension method in the format of the required lambda expression. This extension method is pigging backing on some Telerik controls, and I'm trying to define some standards among the us of the controls in my department. (I still haven't to figuring out how to the int value out either lol).

I've tried to document it and name specific pieces of code the best I could!

class Program
{
    static void Main(string[] args)
    {
        var db = new CustomerDatabase<MyCustomers>();
        db.Customers.Add(new MyCustomers { Id = 1, Name = "Gandalf the Grey" });
        db.Customers.Add(new MyCustomers { Id = 2, Name = "Bilbo Baggins" }); 
        //Was watching Fellowship of the Ring while doing this lol

        //This foreach is similar to the foreach that would be 
        //in my MVC view where db.Customers is really my ViewModel
        foreach (var item in db.Customers)
        {
            //This is where my trouble begins... :(
            Helpers.WriteIdToConsole(item, item.Id);
            Helpers.WriteIdToConsole(item => );
        }
    }

    /// <summary>
    /// CLASS #1
    /// Serves as a holding container for records
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    public class CustomerDatabase<TModel>
        where TModel : class
    {
        public List<TModel> Customers { get;set; }
    }

    /// <summary>
    /// CLASS #2
    /// Simple 'model' class in an MVC View
    /// </summary>
    public class MyCustomers
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    /// <summary>
    /// CLASS #3
    /// Replicates a custom HtmlExtension class I'm developing
    /// </summary>
    public static class Helpers
    {
        /// <summary>
        /// Method #A
        /// One of the methods I'm working on
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        public static void WriteIdToConsole<T>(Expression<Func<T, int>> expression)
            where T : class 
        {
            var id = 999;
            Console.WriteLine($"Id: {id}");
        }
    }
}

Update #1

Here is what I have access to in my Razor View. The method cols.NavigationButton is my extension method that I'm trying to build. I have to create a new instance of a class of type Event or of type TObj if keeping with Kirill Shlenskiy's suggested answer below.

@model List<Event>

@(Html.Kendo().Grid<Event>()
      .Name("pagingGrid")
      .Columns(cols =>
      {
          cols.NavigationButton("Edit", "Edit", "Event",new Event(), p => p.Id);
          cols.Bound(p => p.Name);
      })
      .StandardPagingGrid("Event", "Administration")
)

Upvotes: 3

Views: 10208

Answers (2)

Kirill Shlenskiy
Kirill Shlenskiy

Reputation: 9587

In your question you say you're still figuring out "how to get the int value out", so I'll assume that you're after a more generic implementation which can give you the name of the member being accessed and its value. Here's a snippet for you:

public static void WriteToConsole<TObj, TMember>(TObj obj, Expression<Func<TObj, TMember>> expression)
{
    MemberExpression memberExpr = (MemberExpression)expression.Body;
    string memberName = memberExpr.Member.Name;
    Func<TObj, TMember> compiledDelegate = expression.Compile();
    TMember value = compiledDelegate(obj);

    Console.WriteLine($"{memberName}: {value}");
}

Usage:

var dummy = new Dummy { ID = 123 };

WriteToConsole(dummy, d => d.ID); // Outputs "ID: 123"

I have removed the generic reference type constraint (where T : class) as this will work fine on value types too.

Key thing to note is that in order to extract the value you need not just the expression capable of doing so, but also an instance to pass to the delegate compiled from the expression (so you were on the right track with the first Helpers.WriteIdToConsole call in your code - you just needed to get it to the finish line).

Edit

If you only care about extracting basic member information from the expression, you can use a slightly different approach:

public static MemberInfo Member<T>(Expression<Func<T, object>> expr)
{
    // This is a tricky case because of the "object" return type.
    // Providing an expression which has a value return type will
    // result in a UnaryExpression body, whereas an expression which
    // has a reference return type will have a MemberExpression Body.
    UnaryExpression unaryExpr = expr.Body as UnaryExpression;
    MemberExpression memberExpr = (MemberExpression)(unaryExpr == null ? expr.Body : unaryExpr.Operand);

    return memberExpr.Member;
}

Basic usage:

MemberInfo id = Member<MyCustomers>(item => item.Id);

Console.WriteLine(id.Name); // Outputs "Id"

The way you would plug it in:

public static void NavigationButton<T>(string ..., string ..., string ..., Expression<Func<T, object>> expression)
{
    MemberInfo member = Member(expression);

    // Do something with member, cast to PropertyInfo if needed etc.
}

...

NavigationButton<MyCustomers>("Edit", "Edit", "Event", p => p.Id);

Upvotes: 3

Jenish Rabadiya
Jenish Rabadiya

Reputation: 6766

You are required to pass Expression to the method and also need to specify the type argument explicitly.

You may use/call that method(WriteIdToConsole) like following

foreach (var item in db.Customers)
{
    Helpers.WriteIdToConsole<MyCustomers>(myCustomer => myCustomer.Id);
}

Here is the dot net fiddle for full code review.

Upvotes: 2

Related Questions