84 votes

C # - Méthode correcte pour charger l'assembly, les méthodes Find Class et Call Run ()

Exemple de programme en mode console.

class Program
{
    static void Main(string[] args)
    {
        // ... code to build dll ... not written yet ...
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        // don't know what or how to cast here
        // looking for a better way to do next 3 lines
        IRunnable r = assembly.CreateInstance("TestRunner");
        if (r == null) throw new Exception("broke");
        r.Run();

    }
}

Je veux créer dynamiquement une assemblée (.dll), puis charger l'assembly, instancier une classe, et d'appeler la méthode Run() de cette classe. Devrais-je essayer de casting le Lanceur de classe à quelque chose? Pas sûr de savoir comment les types dans une assemblée (code dynamique) permettrait de savoir à propos de mes types dans mon (statique de l'assemblée shell / de l'application). Est-il préférable de l'utiliser juste quelques lignes de code de réflexion pour appeler Run() sur un objet? Ce que le code devrait ressembler?

Mise à JOUR: William Edmondson - voir le commentaire

78voto

cdiggins Points 5549

Utiliser un AppDomain

Il est plus sûr et plus souple pour charger l'assembly dans sa propre AppDomain première.

Ainsi, au lieu de la réponse donnée précédemment:

var asm = Assembly.LoadFile(@"C:\myDll.dll");
var type = asm.GetType("TestRunner");
var runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

Je suggère le suivant (adapté de cette réponse à une question connexe):

var domain = AppDomain.CreateDomain("NewDomainName");
var pathToDll = @"C:\myDll.dll"; 
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(pathToDll, t.FullName) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

Maintenant, vous pouvez décharger l'assemblée et ont des paramètres de sécurité différents.

Si vous voulez encore plus de souplesse et de puissance dynamique pour le chargement et le déchargement des assemblages, vous devriez regarder les compléments Gérés Cadre (c'est à dire l' System.AddIn d'espace de noms). Pour plus d'informations, voir cet article sur les compléments et les possibilités d'extension sur le site MSDN.

50voto

Jeff Sternal Points 30147

Si vous n'avez pas accès aux informations de type TestRunner dans l'assembly appelant (vous semblez ne pas pouvoir le faire), vous pouvez appeler la méthode de la manière suivante:

 Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type     type     = assembly.GetType("TestRunner");
var      obj      = Activator.CreateInstance(type);

// Alternately you could get the MethodInfo for the TestRunner.Run method
type.InvokeMember("Run", 
                  BindingFlags.Default | BindingFlags.InvokeMethod, 
                  null,
                  obj,
                  null);
 

Si vous avez accès au type d'interface IRunnable , vous pouvez y envoyer votre instance (plutôt que le type TestRunner implémenté dans l'assembly créé ou chargé dynamiquement, n'est-ce pas?) :

   Assembly assembly  = Assembly.LoadFile(@"C:\dyn.dll");
  Type     type      = assembly.GetType("TestRunner");
  IRunnable runnable = Activator.CreateInstance(type) as IRunnable;
  if (runnable == null) throw new Exception("broke");
  runnable.Run();
 

12voto

Chris Doggett Points 9987

Je suis en train de faire exactement ce que vous cherchez dans mon moteur de règles, qui utilise CS-Script dynamique de la compilation, le chargement et l'exécution de C#. Il devrait être facilement traduisibles en ce que vous cherchez, et je vais vous donner un exemple. D'abord, le code (stripped-down):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScriptLibrary;

namespace RulesEngine
{
    /// <summary>
    /// Make sure <typeparamref name="T"/> 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.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RulesEngine<T> 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<T>();

            RulesLastModified = lastModified;
        }
    }
}

Ceci prendra une interface de type T, de compiler une .cs fichier dans une assemblée, d'instancier une classe d'un type donné, et de les aligner que instancié de la classe à l'interface. Fondamentalement, vous avez juste à vous assurer que le instancié de la classe implémentant cette interface. J'utilise les propriétés de configuration et d'accès à tout, comme ceci:

private RulesEngine<IRulesEngine> rulesEngine;

public RulesEngine<IRulesEngine> RulesEngine
{
    get
    {
        if (null == rulesEngine)
        {
            string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");

            rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName);
        }

        return rulesEngine;
    }
}

public IRulesEngine RulesEngineInterface
{
    get { return RulesEngine.Interface; }
}

Pour votre exemple, vous voulez appeler Run(), donc je voudrais faire une interface qui définit la méthode Run (), comme ceci:

public interface ITestRunner
{
    void Run();
}

Ensuite, faire une classe qui l'implémente, comme ceci:

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

Changer le nom de RulesEngine à quelque chose comme TestHarness, et l'ensemble de vos propriétés:

private TestHarness<ITestRunner> testHarness;

public TestHarness<ITestRunner> TestHarness
{
    get
    {
        if (null == testHarness)
        {
            string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");

            testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName);
        }

        return testHarness;
    }
}

public ITestRunner TestHarnessInterface
{
    get { return TestHarness.Interface; }
}

Ensuite, n'importe où vous voulez l'appeler, il vous suffit d'exécuter:

ITestRunner testRunner = TestHarnessInterface;

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

Il serait sans doute grand travail pour un système de plugin, mais mon code est est limitée à chargement et l'exécution d'un fichier, étant donné que toutes nos règles sont en C# fichier source. Je pense que ce serait assez facile à modifier qu'il vient de passer dans le type de/le fichier source pour chaque vous envie de courir, de si. Vous avez juste à déplacer le code de la lecture dans une méthode qui a pris ces deux paramètres.

Aussi, utilisez votre IRunnable en place de ITestRunner.

6voto

William Edmondson Points 1962

Vous devrez utiliser la réflexion pour obtenir le type "TestRunner". Utilisez la méthode Assembly.GetType.

 class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        Type type = assembly.GetType("TestRunner");
        var obj = (TestRunner)Activator.CreateInstance(type);
        obj.Run();
    }
}
 

2voto

280Z28 Points 49515

Lorsque vous construisez votre assemblée, vous pouvez appeler AssemblyBuilder.SetEntryPoint, et le récupérer à partir de l' Assembly.EntryPoint de la propriété de l'invoquer.

Gardez à l'esprit que vous aurez envie d'utiliser cette signature, et note qu'il n'a pas à être nommé Main:

static void Run(string[] args)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X