Correct Way to Load Assembly, Find Class and Call Run() Method

前端 未结 5 955
刺人心
刺人心 2020-11-22 12:17

Sample console program.

class Program
{
    static void Main(string[] args)
    {
        // ... code to build dll ... not written yet ...
        Assembly a         


        
5条回答
  •  一整个雨季
    2020-11-22 12:41

    I'm doing exactly what you're looking for in my rules engine, which uses CS-Script for dynamically compiling, loading, and running C#. It should be easily translatable into what you're looking for, and I'll give an example. First, the code (stripped-down):

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using CSScriptLibrary;
    
    namespace RulesEngine
    {
        /// 
        /// Make sure  is an interface, not just any type of class.
        /// 
        /// Should be enforced by the compiler, but just in case it's not, here's your warning.
        /// 
        /// 
        public class RulesEngine where T : class
        {
            public RulesEngine(string rulesScriptFileName, string classToInstantiate)
                : this()
            {
                if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName");
                if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate");
    
                if (!File.Exists(rulesScriptFileName))
                {
                    throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName);
                }
    
                RulesScriptFileName = rulesScriptFileName;
                ClassToInstantiate = classToInstantiate;
    
                LoadRules();
            }
    
            public T @Interface;
    
            public string RulesScriptFileName { get; private set; }
            public string ClassToInstantiate { get; private set; }
            public DateTime RulesLastModified { get; private set; }
    
            private RulesEngine()
            {
                @Interface = null;
            }
    
            private void LoadRules()
            {
                if (!File.Exists(RulesScriptFileName))
                {
                    throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName);
                }
    
                FileInfo file = new FileInfo(RulesScriptFileName);
    
                DateTime lastModified = file.LastWriteTime;
    
                if (lastModified == RulesLastModified)
                {
                    // No need to load the same rules twice.
                    return;
                }
    
                string rulesScript = File.ReadAllText(RulesScriptFileName);
    
                Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true);
    
                @Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface();
    
                RulesLastModified = lastModified;
            }
        }
    }
    

    This will take an interface of type T, compile a .cs file into an assembly, instantiate a class of a given type, and align that instantiated class to the T interface. Basically, you just have to make sure the instantiated class implements that interface. I use properties to setup and access everything, like so:

    private RulesEngine rulesEngine;
    
    public RulesEngine RulesEngine
    {
        get
        {
            if (null == rulesEngine)
            {
                string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");
    
                rulesEngine = new RulesEngine(rulesPath, typeof(Rules).FullName);
            }
    
            return rulesEngine;
        }
    }
    
    public IRulesEngine RulesEngineInterface
    {
        get { return RulesEngine.Interface; }
    }
    

    For your example, you want to call Run(), so I'd make an interface that defines the Run() method, like this:

    public interface ITestRunner
    {
        void Run();
    }
    

    Then make a class that implements it, like this:

    public class TestRunner : ITestRunner
    {
        public void Run()
        {
            // implementation goes here
        }
    }
    

    Change the name of RulesEngine to something like TestHarness, and set your properties:

    private TestHarness testHarness;
    
    public TestHarness TestHarness
    {
        get
        {
            if (null == testHarness)
            {
                string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");
    
                testHarness = new TestHarness(sourcePath , typeof(TestRunner).FullName);
            }
    
            return testHarness;
        }
    }
    
    public ITestRunner TestHarnessInterface
    {
        get { return TestHarness.Interface; }
    }
    

    Then, anywhere you want to call it, you can just run:

    ITestRunner testRunner = TestHarnessInterface;
    
    if (null != testRunner)
    {
        testRunner.Run();
    }
    

    It would probably work great for a plugin system, but my code as-is is limited to loading and running one file, since all of our rules are in one C# source file. I would think it'd be pretty easy to modify it to just pass in the type/source file for each one you wanted to run, though. You'd just have to move the code from the getter into a method that took those two parameters.

    Also, use your IRunnable in place of ITestRunner.

提交回复
热议问题