Udostępnij za pośrednictwem


Compiling code at runtime and MVP program is 20

This month marks the 20th anniversary of the Microsoft MVP program, so MVPs were in the news.

Apparently I had a lot to do with the founding of the program J

Wikipedia: https://en.wikipedia.org/wiki/Microsoft_Most_Valuable_Professional

Microsoft News: https://www.microsoft.com/en-us/news/features/2013/feb13/02-21MVP.aspx

Microsoft MSDN blog: MVP Award 20 Year Anniversary

I was contemplating a few ways to distribute some code snippets to be executed at runtime. These snippets would be real .Net expressions, dealing with custom types. The running application could have been compiled months ago, but the expressions could be received daily.

1. Distribute a plain text file: the target program would parse the file and try to compile the expressions

2. Distribute an XML file: the structure is a little more rigid. Runtime compile still necessary

3. Distribute a compiled DLL: we know it compiles, but it’s not very interesting

So it sounded interesting to explore runtime compilation of code represented as a string, referencing real application types.

The code below takes a sample string representing the contents of a C# file and compiles it into a different in-memory assembly. It contains references to a class from the 1st assembly (MyDataClass)

MyDataClass contains a member called “predicate” which is a Boolean expression.

At runtime, the code

1. takes the string, compiles it into an in-memory assembly

2. finds a member on that class that returns an array of MyDataClass

3. instantiates the class from the newly compiled assembly

4. Invokes that member to get the array of MyDataClass

5. For each instance of MyDataClass, invoke the predicate, passing the string “MVP”, showing the Result in a 3rd column

6. Show the results in 3 columns

In the sample, the MyDataClass predicate instances can have different predicates, perhaps based on the member information or the passed in parameter.

Note: the code works regardless of the name of the runtime class and method because the code looks for the method/class via reflection.

Start Visual Studio, File->New->Project->C#->Windows->WPF Application

Paste in the sample code below to replace MainWindow.Xaml.cs

See also:

Use DataTemplates and WPF in code to create a general purpose LINQ Query results display

<code>

 using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.CSharp;



namespace WpfApplication1
{
  public class MyDataClass
  {
    public string Name;
    public string Description;
    public Func<MyDataClass, string, bool> predicate;
    public string Result;
    public override string ToString()
    {
      return string.Format("{0,-20} {1,-20} {2}", 
        Name, 
        Description, 
        Result);
    }
  }
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Width = 1500;
      this.Loaded += (o, e) =>
      {
        try
        {
          this.Content = string.Empty;
          var cdprovider = CodeDomProvider.CreateProvider("C#");
          var parms = new CompilerParameters();
          parms.ReferencedAssemblies.Add("system.dll");
          parms.ReferencedAssemblies.Add("system.linq.dll");
          //// add reference to self
          parms.ReferencedAssemblies.Add(
              Assembly.GetExecutingAssembly().Location
              );
          parms.GenerateInMemory = true;

          var PlainTxtSrc = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WpfApplication1
{
  public class MyClass
  {
    public override string ToString()
    {
      return ""here i am in MyClass"";
    }

    // return an array of types
    public MyDataClass[] GetData()
    {
      var res = new List<MyDataClass>()
        {
          new MyDataClass() { 
            Name=""Microsoft"",
            Description=""MVP program"",
            predicate=(data, str)=>
              {
                return data.Name.Contains(str);
              },
          },
          new MyDataClass() { 
            Name=""MVP"",
            Description=""Hi from MVP"",
            predicate=(data, str)=>
              { // the predicates can be different
                return data.Description.Contains(str);
              },
          },
          new MyDataClass() { 
            Name=""Program"",
            Description=""20 Years old!"",
            predicate=(data, str)=>
              {
                return data.Description.Contains(str);
              },
          },
        };
      return res.ToArray();
    }
  }
}
";
          // now compile it
          var resCompile = cdprovider.CompileAssemblyFromSource(
            parms,
            PlainTxtSrc);
          if (resCompile.Errors.HasErrors)
          {
            this.Content = new ListView() { ItemsSource = resCompile.Errors };
          }
          else
          {
            foreach (var pubType in
              resCompile.CompiledAssembly.GetExportedTypes())
            {
              foreach (var meth in pubType.GetMethods())
              {
                // does a method return an array of MyDataClass?
                if (meth.ReturnType.Name ==
                     typeof(MyDataClass).Name + "[]" &&
                  // no parameters
                    meth.GetParameters().Length == 0
                  )
                {
                  //create an instance of the class
                  var myType = Activator.CreateInstance(pubType);
                  // invoke the method to get the data
                  var resMyDataClassArray = (MyDataClass[])meth.Invoke(myType, null);
                  
                  // invoke the predicate on the methods
                  foreach (var aMyDataClass in resMyDataClassArray)
                  {
                    aMyDataClass.Result = aMyDataClass.predicate(aMyDataClass, "MVP").ToString();
                  }
                  // show the data
                  this.Content = new ListView()
                  {
                    ItemsSource = resMyDataClassArray,
                    FontFamily = new FontFamily("Courier New")
                  };
                }
              }
            }
          }
        }
        catch (Exception ex)
        {
          this.Content = ex.ToString();
        }
      };
    }
  }
}

</code>