2015-04-15 1 views
2

J'essaie d'utiliser Generics with Reflection dans C# pour construire une méthode capable de gérer plusieurs classes. J'utilise une DLL tierce qui a un tas de classes et sur ces classes, il y a une méthode que j'appelle. Ils renvoient tous des types de retour différents, mais je fais le même traitement une fois que je récupère l'objet (dans mon exemple ci-dessous, ce serait AreaA et AreaB). Fondamentalement, je veux développer une méthode qui prend le nom de classe et le type de retour attendu en tant que variables génériques, puis appelle la méthode correcte (methodName) qui est fourni en tant que paramètre de cette méthode.Réflexion utilisant des génériques et liaison tardive. Comment lancer à l'exécution?

Le programme ci-dessous compile bien et s'exécute sans erreur, mais le problème est le type attendu de la variable 'area'. Dans les instructions ci-dessous, la première ligne est castée en (TArea), et si je survole dessus Dans Visual Studio, l'intellisense montre la propriété 'nom', mais en tapant area.name ne me donne pas la valeur. Je dois taper (zone (AreaA)) .name.

Le problème est le type 'AreaA' pourrait être un autre type au moment de l'exécution. Dans cet exemple, 'AreaB' pour que je puisse coder en dur une distribution.

Comment puis-je accomplir la coulée de la variable « zone » au type approprié me permettant de voir les méthodes/propriétés publiques de la classe de 3e parties? REMARQUE: Dans mon exemple, tout est dans la même classe, mais en réalité les définitions de ServiceA, ServiceB, AreaA et AreaB seront dans une DLL tierce.

Comme toujours, merci à l'avance!

Figure 1 - propriété 'zone' variable ne peut obtenir 'nom' si casté en 'AreaA'

area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" }); 
AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" }); 

Figure 2. - Programme complet

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Reflection; 
using System.IO; 


namespace ReflectionExample 
{ 
    class Sample 
    { 
     class ServiceA 
     { 
      public int size {get; set;} 
      public string name {get; set;} 

      public ServiceA() 
      { 
       name = "TestA"; 
       size = 100; 
      } 
      public AreaA doWork(string name) 
      { 
       return new AreaA(name); 

      } 
     } 

     class AreaA 
     { 
      public string name { get; set;} 
      public AreaA(string name) 
      { 
       this.name = name; 
      } 
      public AreaA() 
      { 

      } 

     } 

     class ServiceB 
     { 
      public int size { get; set; } 
      public string name { get; set; } 

      public ServiceB() 
      { 
       name = "TestB"; 
       size = 50; 
      } 
      public AreaB doWork(string name) 
      { 
       return new AreaB(name); 
      } 

     } 

     class AreaB 
     { 
      public string name { get; set; } 
      public AreaB(string name) 
      { 
       this.name = name; 
      } 
      public AreaB() 
      { 

      } 
     } 

     static void Main(string[] args) 
     { 
      runService<ServiceA, AreaA>("doWork"); 
     } 

     private static void runService<TService, TArea>(string methodName) 
      where TService : class, new() 
      where TArea : class, new() 
     { 
      //Compile time processing 
      Type areaType = typeof(TArea); 
      Type serviceType = typeof(TService); 


      //Print the full assembly name and qualified assembly name 
      Console.WriteLine("AreaType--Full assembly name:\t {0}.", areaType.Assembly.FullName.ToString());   // Print the full assembly name. 
      Console.WriteLine("AreaType--Qualified assembly name:\t {0}.", areaType.AssemblyQualifiedName.ToString()); // Print the qualified assembly name. 
      Console.WriteLine("ServiceType--Full assembly name:\t {0}.", serviceType.Assembly.FullName.ToString());   // Print the full assembly name. 
      Console.WriteLine("ServiceType--Qualified assembly name:\t {0}.", serviceType.AssemblyQualifiedName.ToString()); // Print the qualified assembly name. 

      //This is done because in my code, the assembly doesn't reside in the executiy assembly, it is only setup as a reference 
      var assembly = Assembly.Load(serviceType.Assembly.FullName);   

      //Initialize the generic area 
      TArea area = default(TArea); 
      //Get an instance of the service so I can invoke the method later on 
      var instance = Activator.CreateInstance(serviceType); 

      //Get the methodInfo for the methodName supplied to the runService method 
      MethodInfo dfpMethod = serviceType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); 

      //area is type casted to (TArea), the intellisense shows the property 'name', but typing area.name doesn't give me the value 
      //I have to type ((AreaA)area).name. Problem is the type 'AreaA' could be another type. In this example, 'AreaB' 
      area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" }); 
      AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" }); 
      Console.WriteLine(); 

     } 

    } 
} 
+0

en tant que nœud de côté: si vous attendez plusieurs appels à 'runService', je pense que vous devriez en faire une classe afin que vous ne avoir à faire une fois le chargement de l'assembly et la création de l'instance de service. En outre, vous pouvez mettre en cache les informations de méthode pour une méthode dans un dictionnaire par nom de méthode (ou mieux encore créer un délégué à partir des informations de méthode). – Alex

Répondre

0

La source de votre erreur est que vous jeter toutes vos valeurs de retour à taper TAREA avec l'énoncé:

TArea area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" }); 

par votre spécification générique, la seule chose promise à vous par le type TAREA est que c'est une classe. Par conséquent, TArea ne vous donne accès à rien d'autre qu'aux membres du type 'objet'.

Au lieu de cela, faire disparaître l'argument générique TAREA en faveur de l'utilisation du mot-clé « dynamique »:

var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" }); 
return area.name; // no error 

Notez que ceci est pertinent que si les types réels AreaA et AreaB sont définies dans les bibliothèques de tiers (comme vous le dites) et vous ne pouvez pas les modifier. Si vous ne pouvez pas modifier les classes, vous ne pouvez pas introduire une interface (ce dont vous avez vraiment besoin ici). Si vous ne pouvez pas introduire une interface, mais que tous les types exposent des signatures identiques, vous pouvez supposer l'existence des membres appropriés en utilisant le type dynamique.

Si vous devez faire beaucoup de travail avec AreaA/AreaB et vous ne voulez pas les frais généraux de performance de toutes les opérations dynamiques, définir votre propre classe généralisée qui expose toutes les signatures dont vous avez besoin:

public class MyGenericArea 
{ 
    public MyGenericArea(string name) 
    { 
     this.Name = name; 
    } 
    public string Name {get; set;} 
} 

Ensuite, renseignez la classe en utilisant la coulée dynamique et le retour de ce type de classe au lieu:

var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" }); 
return new MyGenericArea(area.name); 
+0

J'ai utilisé cette approche et probablement aussi chercher des façons d'optimiser les performances en faisant ce que le commentaire ci-dessus a mentionné à propos de la construction d'un délégué à partir des informations de méthode. – FDot

0

Je pense que le problème que vous aurez ici est de mélanger les types chargés dynamiquement (Assembly.Load()) avec des types qu'un re référencé directement à partir de votre projet et vous pouvez voir dans IntelliSense, à savoir AreaA. Si vous chargez dynamiquement des assemblages entiers en utilisant la réflexion, intellisense ne fera rien pour vous aider à voir les membres de la classe, car cette information doit être connue au moment de la compilation, et par définition vous chargez les assemblages lors de l'exécution.

Si vous voulez juste voir une liste de toutes les propriétés publiques disponibles à votre type, vous pouvez utiliser ceci:

var areaProperties = area.GetType().GetProperties(); 

Mais encore une fois, cela se fait au moment de l'exécution il aidera pas vous lors de l'écriture du code.

Vous pouvez dynamiquement lire la valeur du « nom » propriété à l'aide:

var nameValue = area.GetType().GetProperty("name").GetValue(area); 

Essentiellement, si vous voulez IntelliSense, référence directement de la dll de votre projet Visual Studio plutôt que d'utiliser Assembly.Load(). Espérons que cela aide.

+0

Merci! GetProperty ("name"). GetValue (area) aide car je peux au moins obtenir les valeurs dont j'ai besoin dynamiquement. BTW, j'ai seulement inclus AreaA et AreaB dans la même classe pour cet exemple, ils n'existent pas dans l'assembly où le code s'exécute réellement. Je vais tester et voir ce qui se passe – FDot