別のアセンブリのクラス名からタイプを解決する


87

クラスの型を解決する必要があるメソッドがあります。このクラスは、次のような名前空間を持つ別のアセンブリに存在します。

MyProject.Domain.Model

私は以下を実行しようとしています:

Type.GetType("MyProject.Domain.Model." + myClassName);

これは、このアクションを実行しているコードが、解決しようとしているタイプのクラスと同じアセンブリにある場合にうまく機能しますが、クラスが別のアセンブリにある場合、このコードは失敗します。

このタスクを実行するためのはるかに優れた方法があると確信していますが、探しているクラスのタイプを解決するためにアセンブリを解決し、内部の名前空間をトラバースした経験はあまりありません。このタスクをより優雅に達成するためのアドバイスやヒントはありますか?


回答:


171

次のようにアセンブリ名を追加する必要があります。

Type.GetType("MyProject.Domain.Model." + myClassName + ", AssemblyName");

あいまいさを回避するため、またはアセンブリがGACにある場合は、次のような完全修飾アセンブリ名を指定する必要があります。

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

すばらしい、私はアセンブリを含むようなマイナーな何かが欠けていることを知っていました。このソリューションは私のニーズに合っていました。ありがとう。
ブランドン

11
シリアル化を扱う人のために:アセンブリ修飾名を取得するには、プロパティType.AssemblyQualifiedName
Michael Wild

1
タイプがList <T>で、Tがカスタムクラスの場合、2つのアセンブリをどのように指定しますか?つまり、System.Collections.Generic.Listのmscorlibアセンブリ、およびTを含むライブラリ?
サイモングリーン

@SimonGreen:おそらくを使用して構築する必要がありますlistType.MakeGenericType(itemType)。両方の型変数はType.GetType()、私の答えのように使用して構築できます。
サンドールDrieënhuizen

object.Assembly.ToString()完全なアセンブリを取得するためにも使用できます。
zezba9000 2018

7

この普遍的な解決策は、ロードする必要がある人々のためにあるダイナミック外部参照からジェネリック型をすることによってAssemblyQualifiedNameアセンブリからジェネリック型のすべての部分があり、そこから知らなくても、:

    public static Type ReconstructType(string assemblyQualifiedName, bool throwOnError = true, params Assembly[] referencedAssemblies)
    {
        foreach (Assembly asm in referencedAssemblies)
        {
            var fullNameWithoutAssemblyName = assemblyQualifiedName.Replace($", {asm.FullName}", "");
            var type = asm.GetType(fullNameWithoutAssemblyName, throwOnError: false);
            if (type != null) return type;
        }

        if (assemblyQualifiedName.Contains("[["))
        {
            Type type = ConstructGenericType(assemblyQualifiedName, throwOnError);
            if (type != null)
                return type;
        }
        else
        {
            Type type = Type.GetType(assemblyQualifiedName, false);
            if (type != null)
                return type;
        }

        if (throwOnError)
            throw new Exception($"The type \"{assemblyQualifiedName}\" cannot be found in referenced assemblies.");
        else
            return null;
    }

    private static Type ConstructGenericType(string assemblyQualifiedName, bool throwOnError = true)
    {
        Regex regex = new Regex(@"^(?<name>\w+(\.\w+)*)`(?<count>\d)\[(?<subtypes>\[.*\])\](, (?<assembly>\w+(\.\w+)*)[\w\s,=\.]+)$?", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
        Match match = regex.Match(assemblyQualifiedName);
        if (!match.Success)
            if (!throwOnError) return null;
            else throw new Exception($"Unable to parse the type's assembly qualified name: {assemblyQualifiedName}");

        string typeName = match.Groups["name"].Value;
        int n = int.Parse(match.Groups["count"].Value);
        string asmName = match.Groups["assembly"].Value;
        string subtypes = match.Groups["subtypes"].Value;

        typeName = typeName + $"`{n}";
        Type genericType = ReconstructType(typeName, throwOnError);
        if (genericType == null) return null;

        List<string> typeNames = new List<string>();
        int ofs = 0;
        while (ofs < subtypes.Length && subtypes[ofs] == '[')
        {
            int end = ofs, level = 0;
            do
            {
                switch (subtypes[end++])
                {
                    case '[': level++; break;
                    case ']': level--; break;
                }
            } while (level > 0 && end < subtypes.Length);

            if (level == 0)
            {
                typeNames.Add(subtypes.Substring(ofs + 1, end - ofs - 2));
                if (end < subtypes.Length && subtypes[end] == ',')
                    end++;
            }

            ofs = end;
            n--;  // just for checking the count
        }

        if (n != 0)
            // This shouldn't ever happen!
            throw new Exception("Generic type argument count mismatch! Type name: " + assemblyQualifiedName);  

        Type[] types = new Type[typeNames.Count];
        for (int i = 0; i < types.Length; i++)
        {
            try
            {
                types[i] = ReconstructType(typeNames[i], throwOnError);
                if (types[i] == null)  // if throwOnError, should not reach this point if couldn't create the type
                    return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"Unable to reconstruct generic type. Failed on creating the type argument {(i + 1)}: {typeNames[i]}. Error message: {ex.Message}");
            }
        }

        Type resultType = genericType.MakeGenericType(types);
        return resultType;
    }

そして、あなたはこのコード(コンソールアプリ)でそれをテストすることができます

    static void Main(string[] args)
    {
        Type t1 = typeof(Task<Dictionary<int, Dictionary<string, int?>>>);
        string name = t1.AssemblyQualifiedName;
        Console.WriteLine("Type: " + name);
        // Result: System.Threading.Tasks.Task`1[[System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
        Type t2 = ReconstructType(name);
        bool ok = t1 == t2;
        Console.WriteLine("\r\nLocal type test OK: " + ok);

        Assembly asmRef = Assembly.ReflectionOnlyLoad("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
        // Task<DialogResult> in refTypeTest below:
        string refTypeTest = "System.Threading.Tasks.Task`1[[System.Windows.Forms.DialogResult, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
        Type t3 = ReconstructType(refTypeTest, true, asmRef);
        Console.WriteLine("External type test OK: " + (t3.AssemblyQualifiedName == refTypeTest));

        // Getting an external non-generic type directly from references:
        Type t4 = ReconstructType("System.Windows.Forms.DialogResult, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", true, asmRef);

        Console.ReadLine();
    }

私と同じ問題を抱えている人々を支援するためのソリューションを共有しています(外部参照アセンブリで部分的または全体的に定義できる文字列から任意の型を逆シリアル化し、参照はアプリのユーザーによって動的に追加されます)。

それが誰にでも役立つことを願っています!


2

OPと同様に、名前で型の限定されたサブセットをロードする必要がありました(私の場合、すべてのクラスが単一のアセンブリにあり、同じインターフェイスを実装していました)。Type.GetType(string)別のアセンブリに対して使用しようとすると(他の投稿で言及されているようにAssemblyQualifiedNameを追加することさえ)、多くの奇妙な問題が発生しました。これが私が問題を解決した方法です:

使用法:

var mytype = TypeConverter<ICommand>.FromString("CreateCustomer");

コード:

    public class TypeConverter<BaseType>
        {
            private static Dictionary<string, Type> _types;
            private static object _lock = new object();

            public static Type FromString(string typeName)
            {
                if (_types == null) CacheTypes();

                if (_types.ContainsKey(typeName))
                {
                    return _types[typeName];
                }
                else
                {
                    return null;
                }
            }

            private static void CacheTypes()
            {
                lock (_lock)
                {
                    if (_types == null)
                    {
                        // Initialize the myTypes list.
                        var baseType = typeof(BaseType);
                        var typeAssembly = baseType.Assembly;
                        var types = typeAssembly.GetTypes().Where(t => 
                            t.IsClass && 
                            !t.IsAbstract && 
                            baseType.IsAssignableFrom(t));

                        _types = types.ToDictionary(t => t.Name);
                    }
                }
            }
        }

明らかに、CacheTypesメソッドを微調整して、AppDomain内のすべてのアセンブリ、またはユースケースにより適した他のロジックを検査できます。ユースケースで複数の名前空間から型をロードできる場合は、FullName代わりに型を使用するように辞書キーを変更することをお勧めします。または、型が共通のインターフェイスまたは基本クラスから継承されていない場合は、を削除して<BaseType>、CacheTypesメソッドを次のようなものを使用するように変更できます。.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")


1

最初にアセンブリをロードし、次にタイプをロードします。例:アセンブリDLL = Assembly.LoadFile(PATH); DLL.GetType(typeName);


0

標準的な方法のいずれかを使用できますか?

typeof( MyClass );

MyClass c = new MyClass();
c.GetType();

そうでない場合は、アセンブリに関する情報をType.GetTypeに追加する必要があります。


0

AssemblyQualifiedNameプロパティを使用した短く動的なアプローチ-

Type.GetType(Type.GetType("MyProject.Domain.Model." + myClassName).AssemblyQualifiedName)

楽しい!


10
Type.GetType( "MyProject.Domain.Model。" + myClassName)が失敗した場合、別のGetType呼び出しでラップすることでそれを防ぐことができますか?
Kaine 2018年

1
いずれの場合も、System.NullReferenceExceptionを使用してtrycatchブロックでラップできます。これで誤解される可能性がはるかに高くなります-"MyProject.Domain.Model.ClassName、ClassName、Version = 2.0.0.0、Culture = neutral、PublicKeyToken = b77a5c561934e089"そしてこれで-"MyProject.Domain.Model。" ...
simonbor 2018

1
@Kaine simonborの意味はわかりませんが、文字列を書き込むときにGetType()。AssemblyQualifiedNameを使用する場合は、文字列を使用して型に解決するときに心配する必要はありません。
セルジオPorres
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.