I've been reading about one of the obscure new features of .Net 2.0
called DynamicMethods. Part of the System.Reflection.Emit
namespace, the DynamicMethod class allows you to create and execute
methods at runtime
without
creating any dynamic assembly or dynamic type.
This by itself sounds interesting, but not very useful.
I mean how often do you have the need to dynamic generate
methods? Except for certain situations, not very often if at
all. But DynamicMethods have one other interesting feature.
If the DynamicMethod is bound to a particular Type, then the generated
method has access to the private members of that type.
Here's a small example (important code only):
public partial class Form1 : Form
{
DynamicMethod _dMethod;
public Form1()
{
InitializeComponent();
_dMethod
= CreateDynamicMethod();
}
private DynamicMethod CreateDynamicMethod()
{
DynamicMethod dmethod = new DynamicMethod("", typeof(int), new Type[] { typeof(TextBoxBase) }, typeof(TextBoxBase));
ILGenerator igen = dmethod.GetILGenerator();
FieldInfo field = typeof(TextBoxBase).GetField("maxLength", BindingFlags.NonPublic | BindingFlags.Instance);
igen.Emit(OpCodes.Ldarg_0);
igen.Emit(OpCodes.Ldfld, field);
igen.Emit(OpCodes.Ret);
return dmethod;
}
private void button1_Click(object sender, EventArgs e)
{
Random r = new Random();
textBox1.MaxLength
= r.Next(1, 4096);
label1.Text
= "Max Length =
" +
textBox1.MaxLength.ToString();
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("Max Length = " + ((int)_dMethod.Invoke(textBox1, new object[] { textBox1 })).ToString());
}
}
This is a simple Windows Application that contains one TextBox, one
Label, and two Buttons. The first button (button2) sets the
MaxLength property of the TextBox to a random number between 1 and 4096
and sets the Label's Text property to that value. Then second
button executes the generated function that was generated at the form's
creation and shows the returned value in a MessageBox. Now
note what the generated function is doing, specifically this line:
FieldInfo field = typeof(TextBoxBase).GetField("maxLength", BindingFlags.NonPublic | BindingFlags.Instance);
The function is using a private instance field called maxLength, which
belongs to the abstract class TextBoxBase (I used Lutz'a Reflector
to find out what the name of the field was). The function
then just returns the value of the field. So the generated
function was able to access the private instance field for
the abstract parent of the TextBox object.
There are two drawbacks with these DynamicMethods when compared to the
Extension Methods of C# 3.0.
1) You must have a reference to the created DynamicMethod
object or the delegate created by the DynamicMethod's CreateDelegate
function.
2) You have to use IL OpCodes to generate the body of your
runtime function.
Now I have to think up some practical examples (not counting dynamic
delegates for events).
Some interesting links:
DebuggerVisualizer
for DynamicMethod (Show me the IL)
To
generate an event handler at run time by using a dynamic method
Fast
late-bound invocation through DynamicMethod delegates
Here's the full code from the example above:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Windows.Forms;
namespace DynamicMethodTest
{
public partial class Form1 : Form
{
DynamicMethod _dMethod;
public Form1()
{
InitializeComponent();
_dMethod
= CreateDynamicMethod();
}
private DynamicMethod CreateDynamicMethod()
{
DynamicMethod dmethod = new DynamicMethod("Hello", typeof(int), new Type[] { typeof(TextBoxBase) }, typeof(TextBoxBase));
ILGenerator igen = dmethod.GetILGenerator();
FieldInfo field = typeof(TextBoxBase).GetField("maxLength", BindingFlags.NonPublic | BindingFlags.Instance);
igen.Emit(OpCodes.Ldarg_0);
igen.Emit(OpCodes.Ldfld, field);
igen.Emit(OpCodes.Ret);
return dmethod;
}
private void button1_Click(object sender, EventArgs e)
{
Random r = new Random();
textBox1.MaxLength
= r.Next(1, 4096);
label1.Text
= "Max Length =
" +
textBox1.MaxLength.ToString();
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("Max Length = " + ((int)_dMethod.Invoke(textBox1, new object[] { textBox1 })).ToString());
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing &&
(components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support
- do not modify
/// the contents of this method with the
code editor.
/// </summary>
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(13, 13);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 0;
//
// button2
//
this.button2.Location = new System.Drawing.Point(205, 9);
this.button2.Name = "button1";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "Execute";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// button1
//
this.button1.Location = new System.Drawing.Point(205, 39);
this.button1.Name = "button2";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 2;
this.button1.Text = "Set Length";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 48);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(75, 13);
this.label1.TabIndex = 3;
this.label1.Text = "Max Length = ";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.label1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.textBox1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Label label1;
}
}