2009-03-19 9 views
3

Dans un assemblage chargé dans le domaine d'application de courant en utilisant Assembly.LoadFrom, j'ai le code suivant:Comment charger un type générique contenant des types imbriqués à partir d'assemblys chargés dynamiquement?

[TypeConverter(typeof(EnumConverter<Shapes>))] 
public enum Shapes 
{ 
    Triangle, 
    Square, 
    Circle 
} 

Le EnumConverter générique <T> est défini dans l'ensemble renvoyée en appelant Assembly.GetEntryAssembly(). Quand je lis l'attribut TypeConverter lors de l'exécution, je reçois le nom complet du type qui ressemble à quelque chose comme:

MyAssembly.EnumConverter`1[[MyDynamicAssembly.Shapes, MyDynamicAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] 

Cependant, quand je l'appelle Type.GetType() sur la chaîne, elle renvoie la valeur null. Je m'attendais à pouvoir obtenir une instance de System.Type. Notez également que MyDynamicAssembly n'a pas été généré par Reflection.Emit (signalé dans l'article MSDN here).

Ce code a été généré sur Visual Studio 2005 en utilisant .NET Framework 2.0.

Est-ce que quelqu'un a trouvé une solution de contournement pour ce bug/limitation? A-t-il été corrigé en 3.5?

Répondre

5

Après avoir suivi une piste d'un MSDN forum posting, j'ai été en mesure de mettre en place une solution générale pour charger des types génériques arbitraires à partir d'assemblys chargés à l'exécution. J'espère que cela aide certaines personnes

public static class TypeHelpers 
{ 
    /// <summary> 
    /// Gets the System.Type with the specified name, performing a case-sensitive search. 
    /// </summary> 
    /// <param name="typeName">The assembly-qualified name of the type to get. See System.Type.AssemblyQualifiedName.</param> 
    /// <param name="throwOnError">Whether or not to throw an exception or return null if the type was not found.</param> 
    /// <param name="ignoreCase">Whether or not to perform a case-insensitive search.</param> 
    /// <returns>The System.Type with the specified name.</returns> 
    /// <remarks> 
    /// This method can load types from dynamically loaded assemblies as long as the referenced assembly 
    /// has already been loaded into the current AppDomain. 
    /// </remarks> 
    public static Type GetType(string typeName, bool throwOnError, bool ignoreCase) 
    { 
     if(string.IsNullOrEmpty(typeName)) 
      throw new ArgumentNullException("typeName"); 

     // handle the trivial case 
     Type type; 
     if((type = Type.GetType(typeName, false, ignoreCase)) != null) 
      return type; 

     // otherwise, perform the recursive search 
     try 
     { 
      return GetTypeFromRecursive(typeName, ignoreCase); 
     } 
     catch(Exception e) 
     { 
      if(throwOnError) 
       throw; 
     } 

     return null; 
    } 

    #region Private Static Helper Methods 

    private static Type GetTypeFromRecursive(string typeName, bool ignoreCase) 
    { 
     int startIndex = typeName.IndexOf('['); 
     int endIndex = typeName.LastIndexOf(']'); 

     if(startIndex == -1) 
     { 
      // try to load the non-generic type (e.g. System.Int32) 
      return TypeHelpers.GetNonGenericType(typeName, ignoreCase); 
     } 
     else 
     { 
      // determine the cardinality of the generic type 
      int cardinalityIndex = typeName.IndexOf('`', 0, startIndex); 
      string cardinalityString = typeName.Substring(cardinalityIndex + 1, startIndex - cardinalityIndex - 1); 
      int cardinality = int.Parse(cardinalityString); 

      // get the FullName of the non-generic type (e.g. System.Collections.Generic.List`1) 
      string fullName = typeName.Substring(0, startIndex); 
      if(typeName.Length - endIndex - 1 > 0) 
       fullName += typeName.Substring(endIndex + 1, typeName.Length - endIndex - 1); 

      // parse the child type arguments for this generic type (recursive) 
      List<Type> list = new List<Type>(); 
      string typeArguments = typeName.Substring(startIndex + 1, endIndex - startIndex - 1); 
      foreach(string item in EachAssemblyQualifiedName(typeArguments, cardinality)) 
      { 
       Type typeArgument = GetTypeFromRecursive(item, ignoreCase); 
       list.Add(typeArgument); 
      } 

      // construct the generic type definition 
      return TypeHelpers.GetNonGenericType(fullName, ignoreCase).MakeGenericType(list.ToArray()); 
     } 
    } 

    private static IEnumerable<string> EachAssemblyQualifiedName(string s, int count) 
    { 
     Debug.Assert(count != 0); 
     Debug.Assert(string.IsNullOrEmpty(s) == false); 
     Debug.Assert(s.Length > 2); 

     // e.g. "[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]" 
     // e.g. "[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.DateTime, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]" 
     // e.g. "[System.Collections.Generic.KeyValuePair`2[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.DateTime, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]" 

     int startIndex = 0; 
     int bracketCount = 0; 

     while(count > 0) 
     { 
      bracketCount = 0; 

      for(int i = startIndex; i < s.Length; i++) 
      { 
       switch(s[i]) 
       { 
        case '[': 
         bracketCount++; 
         continue; 

        case ']': 
         if(--bracketCount == 0) 
         { 
          string item = s.Substring(startIndex + 1, i - startIndex - 1); 
          yield return item; 
          startIndex = i + 2; 
         } 
         break; 

        default: 
         continue; 
       } 
      } 

      if(bracketCount != 0) 
      { 
       const string SR_Malformed = "The brackets are unbalanced in the string, '{0}'."; 
       throw new FormatException(string.Format(SR_Malformed, s)); 
      } 

      count--; 
     } 
    } 

    private static Type GetNonGenericType(string typeName, bool ignoreCase) 
    { 
     // assume the type information is not a dynamically loaded assembly 
     Type type = Type.GetType(typeName, false, ignoreCase); 
     if(type != null) 
      return type; 

     // otherwise, search the assemblies in the current AppDomain for the type 
     int assemblyFullNameIndex = typeName.IndexOf(','); 
     if(assemblyFullNameIndex != -1) 
     { 
      string assemblyFullName = typeName.Substring(assemblyFullNameIndex + 2, typeName.Length - assemblyFullNameIndex - 2); 
      foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 
      { 
       if(assembly.GetName().FullName == assemblyFullName) 
       { 
        string fullName = typeName.Substring(0, assemblyFullNameIndex); 
        type = assembly.GetType(fullName, false, ignoreCase); 
        if(type != null) 
         return type; 
       } 
      } 
     } 

     // no luck? blow up 
     const string SR_TypeNotFound = "The type, '{0}', was not found."; 
     throw new ArgumentException(string.Format(SR_TypeNotFound, typeName), "typeName"); 
    } 

    #endregion 
} 

Ce code a été testé à la fois avec le scénario ci-dessus, et avec le test MbUnit suivant (et peut-être Microsoft va mettre quelque chose d'équivalent dans .NET 4.0?):

[Test] 
public void GetType_DictionaryOfStringAndDictionaryOfInt32AndKeyValuePairOfStringAndListOfInt32() 
{ 
    Dictionary<string, Dictionary<int, KeyValuePair<string, List<int>>>> obj = 
    new Dictionary<string, Dictionary<int, KeyValuePair<string, List<int>>>>(); 

    string typeName = obj.GetType().FullName; 
    Type type = TypeHelpers.GetType(typeName, true, false); 

    Assert.IsTrue(type.Equals(obj.GetType())); 
} 

Remarque: Vous devriez commenter le gestionnaire trivial lorsque vous essayez d'utiliser ce test, sinon Type.GetType() sera appelé à la place du code d'analyse réel.

Questions connexes