Expression Tree Practice: C# Value Types, Reference Types, Generics, Collections, and Function Calling

2019年12月15日 104点热度 0人点赞 1条评论
内容目录

Expression Tree Practical Exercises: C# Value Types, Reference Types, Generics, Collections, Function Calls

[TOC]

img

1. Defining Variables

In C# expression trees, to define a variable, use ParameterExpression.

There are two ways to create variable nodes:

Expression.Parameter()
Expression.Variable()
// Additionally, a constant can be defined using Expression.Constant().

Both methods generate a ParameterExpression type. Both Parameter() and Variable() have two overloads. They create a ParameterExpression node that can be used to identify parameters or variables in the expression tree.

For their usage:

  • Expression.Variable is used to declare local variables within a block.
  • Expression.Parameter is used to declare parameters for input values.

Let’s first look at the first method:

public static ParameterExpression Parameter(Type type)
{
    return Parameter(type, name: null);
}

public static ParameterExpression Variable(Type type)
{
    return Variable(type, name: null);
}

From the code, there seems to be no difference.

Now let’s check the overloads with two parameters:

public static ParameterExpression Parameter(Type type, string name)
{
    Validate(type, allowByRef: true);
    bool byref = type.IsByRef;
    if (byref)
    {
        type = type.GetElementType();
    }

    return ParameterExpression.Make(type, name, byref);
}
public static ParameterExpression Variable(Type type, string name)
{
    Validate(type, allowByRef: false);
    return ParameterExpression.Make(type, name, isByRef: false);
}

As you can see, the only difference between the two is that Parameter allows ref types, whereas Variable does not.

The author could not find specific differences in the official documentation or other articles. After searching on StackOverflow and reviewing the source code, it is confirmed that the difference lies in the fact that Variable cannot use ref types.

Literally, to declare a variable should use Expression.Variable, and to pass function parameters should use Expression.Parameter.

This applies to both value types and reference types.

2. Accessing Properties, Fields, and Methods of Variables/Types

To access properties of variables or types, use:

Expression.Property()

To access properties or fields of variables/types, use:

Expression.PropertyOrField()

To access methods of variables or types, use:

Expression.Call()

Accessing property fields and methods:

Expression.MakeMemberAccess

They all return a MemberExpression type.

Usage-wise, based on whether instantiated or not, there is a small distinction as mentioned above regarding variables or types.

This means that already defined value types or instantiated reference types are variables; types refer to reference types that do not need to be instantiated, such as static types or static property fields/methods.

The above explanation is not very rigorous, and the following examples will explain it gradually.

1. Accessing Properties

Use Expression.Property() or Expression.PropertyOrField() to call properties.

Calling Static Type Properties

Console is a static type, and Console.Title can retrieve the actual location of the compiler program.

Console.WriteLine(Console.Title);

Using the expression tree, it is represented as follows:

MemberExpression member = Expression.Property(null, typeof(Console).GetProperty("Title"));
Expression<Func<string>> lambda = Expression.Lambda<Func<string>>(member);

string result = lambda.Compile()();
Console.WriteLine(result);

Console.ReadKey();

Since the property called is from a static type, the first parameter is null.

The second parameter is a PropertyInfo type.

Calling Instance Properties/Fields

The C# code is as follows:

List<int> a = new List<int>() { 1, 2, 3 };
int result = a.Count;
Console.WriteLine(result);
Console.ReadKey();

In the expression tree, calling an instance property:

ParameterExpression a = Expression.Parameter(typeof(List<int>), "a");
MemberExpression member = Expression.Property(a, "Count");

Expression<Func<List<int>, int>> lambda = Expression.Lambda<Func<List<int>, int>>(member, a);
int result = lambda.Compile()(new List<int> { 1, 2, 3 });
Console.WriteLine(result);

Console.ReadKey();

Aside from Expression.Property(), other methods can be tested by you; I will not elaborate further here.

2. Calling Functions

Use Expression.Call() to call a static type function or an instance function.

Calling Static Type Functions

Taking Console as an example, calling the WriteLine() method:

Console.WriteLine("Calling WriteLine method");

MethodCallExpression method = Expression.Call(
    null,
    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
    Expression.Constant("Calling WriteLine method"));

Expression<Action> lambda = Expression.Lambda<Action>(method);
lambda.Compile()();
Console.ReadKey();

The overloads of Expression.Call() are quite numerous, with common overloads being:

public static MethodCallExpression Call(Expression instance, MethodInfo method, params Expression[] arguments)

Since we are calling a static type function, the first instance is null.

The second method is the overloaded method to be called.

Finally, the last arguments are the parameters being passed.

Calling Instance Functions

Create a class:

public class Test
{
    public void Print(string info)
    {
        Console.WriteLine(info);
    }
}

Call the instance's Print() method:

Test test = new Test();
test.Print("Printed out");
Console.ReadKey();

Represented as follows in expressions:

ParameterExpression a = Expression.Variable(typeof(Test), "test");

MethodCallExpression method = Expression.Call(
    a,
    typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),
    Expression.Constant("Printed out"));

Expression<Action<Test>> lambda = Expression.Lambda<Action<Test>>(method, a);
lambda.Compile()(new Test());
Console.ReadKey();

Note that Expression.Variable(typeof(Test), "test"); only defines a variable without initializing it. For reference types, an instantiation is needed.

The above method is passing an externally instantiated object; later, I will discuss how to instantiate within the expression.

3. Instantiating Reference Types

To instantiate a reference type, use new, and then choose to call the appropriate constructor and set property values.

Now, according to the above steps, let’s discuss them separately.

new

Use Expression.New() to call a type's constructor.

It has five overloads, with two commonly used overloads:

public static NewExpression New(ConstructorInfo constructor);
public static NewExpression New(Type type);

Continuing with the Test type:

NewExpression newA = Expression.New(typeof(Test));

This calls a default constructor with no parameters or a single constructor like shown above.

If you want to specify a particular constructor, you can do:

NewExpression newA = Expression.New(typeof(Test).GetConstructor(xxxxxx));

This will not be explained in detail here.

Assigning Values to Properties

While instantiating a constructor, you can also assign values to properties.

public static MemberInitExpression MemberInit(NewExpression newExpression, IEnumerable<MemberBinding> bindings);

public static MemberInitExpression MemberInit(NewExpression newExpression, params MemberBinding[] bindings);

Both overloads do the same thing.

We will modify the Test class:

public class Test
{
    public int sample { get; set; }
    public void Print(string info)
    {
        Console.WriteLine(info);
    }
}

Then:

var binding = Expression.Bind(
    typeof(Test).GetMember("sample")[0],
    Expression.Constant(10)
);

Creating Reference Types

Expression.MemberInit()

This indicates calling a constructor and initializing one or more members of a new object.

If you want to instantiate a class, you can use

            NewExpression newA = Expression.New(typeof(Test));
            MemberInitExpression test = Expression.MemberInit(newA,
                new List() { }
                );

If you want to assign values to members at instantiation

            NewExpression newA = Expression.New(typeof(Test));
        // Assign a value to a member of type Test
        var binding = Expression.Bind(
            typeof(Test).GetMember("sample")[0], Expression.Constant(10));

        MemberInitExpression test = Expression.MemberInit(newA,
            new List<MemberBinding>() { binding }
            );</code></pre>

Example

Instantiate a type, call the constructor, and assign values to members, the example code is as follows

            // Call the constructor
            NewExpression newA = Expression.New(typeof(Test));
        // Assign a value to a member of type Test
        var binding = Expression.Bind(
            typeof(Test).GetMember("sample")[0], Expression.Constant(10));

        // Instantiate a type
        MemberInitExpression test = Expression.MemberInit(newA,
            new List<MemberBinding>() { binding }
            );

        // Call a method
        MethodCallExpression method1 = Expression.Call(
            test,
            typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),
            Expression.Constant("打印出来")
            );

        // Call a property
        MemberExpression method2 = Expression.Property(test, "sample");

        Expression<Action> lambda1 = Expression.Lambda<Action>(method1);
        lambda1.Compile()();

        Expression<Func<int>> lambda2 = Expression.Lambda<Func<int>>(method2);
        int sample = lambda2.Compile()();
        Console.WriteLine(sample);

        Console.ReadKey();</code></pre>

Four, Instantiate Generic Type and Call

Change the Test class to this

    public class Test
    {
        public void Print(T info)
        {
            Console.WriteLine(info);
        }
    }

The Test class is now a generic class, example of expression instantiation

        static void Main(string[] args)
        {
            RunExpression();
            Console.ReadKey();
        }
        public static void RunExpression()
        {
            // Call the constructor
            NewExpression newA = Expression.New(typeof(Test));
        // Instantiate a type
        MemberInitExpression test = Expression.MemberInit(newA,
            new List<MemberBinding>() { }
            );

        // Call a method
        MethodCallExpression method = Expression.Call(
            test,
            typeof(Test<T>).GetMethod("Print").MakeGenericMethod(new Type[] { typeof(T) }),
            Expression.Constant("打印出来")
            );

        Expression<Action> lambda1 = Expression.Lambda<Action>(method);
        lambda1.Compile()();

        Console.ReadKey();
    }</code></pre>

Five, Define Collection Variable, Initialize, Add Elements

Collection types are represented using ListInitExpression.

To create a collection type, you need to use

ElementInit represents the initial value of a single element in an IEnumerable collection.

ListInit initializes a collection.

In C#, all collections implement IEnumerable and have an Add method or property.

To initialize a collection in C# and add elements, you can do this

            List list = new List()
            {
                "a",
                "b"
            };
            list.Add("666");

In an expression tree, elements are initialized/added using the ElementInit to call the Add method.

Example

            MethodInfo listAdd = typeof(List).GetMethod("Add");
        /*
         * new List<string>()
         * {
         *     "a",
         *     "b"
         * };
         */
        ElementInit add1 = Expression.ElementInit(
            listAdd,
            Expression.Constant("a"),
            Expression.Constant("b")
            );
        // Add("666")
        ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("666"));</code></pre>

Example

            MethodInfo listAdd = typeof(List).GetMethod("Add");
        ElementInit add1 = Expression.ElementInit(listAdd, Expression.Constant("a"));
        ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("b"));
        ElementInit add3 = Expression.ElementInit(listAdd, Expression.Constant("666"));

        NewExpression list = Expression.New(typeof(List<string>));

        // Initialize values
        ListInitExpression setList = Expression.ListInit(
            list,
            add1,
            add2,
            add3
            );
        // No execution, just looking at the output information
        Console.WriteLine(setList.ToString());

        MemberExpression member = Expression.Property(setList, "Count");

        Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(member);
        int result = lambda.Compile()();
        Console.WriteLine(result);

        Console.ReadKey();</code></pre>

痴者工良

高级程序员劝退师

文章评论