Expression Tree Practical Exercises: C# Value Types, Reference Types, Generics, Collections, Function Calls
[TOC]
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>
文章评论