Selesaikan Jenis dari Nama Kelas di Majelis Berbeda

87

Saya memiliki metode di mana saya perlu menyelesaikan Jenis kelas. Kelas ini ada di rakitan lain dengan namespace yang mirip dengan:

MyProject.Domain.Model

Saya mencoba melakukan yang berikut:

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

Ini berfungsi dengan baik jika kode yang melakukan tindakan ini berada di rakitan yang sama dengan kelas yang jenisnya saya coba selesaikan, namun, jika kelas saya berada di rakitan yang berbeda, kode ini gagal.

Saya yakin ada cara yang jauh lebih baik untuk menyelesaikan tugas ini, tetapi saya belum memiliki banyak pengalaman dengan menyelesaikan rakitan dan melintasi ruang nama di dalamnya untuk menyelesaikan jenis kelas yang saya cari. Adakah saran atau tip untuk menyelesaikan tugas ini dengan lebih anggun?

Brandon
sumber

Jawaban:

171

Anda harus menambahkan nama assembly seperti ini:

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

Untuk menghindari ambiguitas atau jika assembly terletak di GAC, Anda harus memberikan nama assembly yang memenuhi syarat seperti:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Sandor Drieënhuizen
sumber
Luar biasa, saya tahu saya melewatkan sesuatu yang kecil seperti termasuk perakitan. Solusi ini berhasil untuk kebutuhan saya. Terima kasih.
Brandon
11
Dan bagi mereka yang berurusan dengan serialisasi: Untuk mendapatkan nama yang memenuhi syarat perakitan, ada properti Type.AssemblyQualifiedName
Michael Wild
1
Jika jenisnya adalah Daftar <T>, di mana T adalah kelas khusus, bagaimana Anda menentukan 2 rakitan? Yaitu. Perakitan mscorlib untuk System.Collections.Generic.List, dan perpustakaan yang berisi T?
Simon Green
@ SimonGreen: Anda mungkin harus membuatnya menggunakan listType.MakeGenericType(itemType). Kedua jenis variabel tersebut dapat dibangun menggunakan Type.GetType()seperti pada jawaban saya.
Sandor Drieënhuizen
object.Assembly.ToString () Dapat digunakan juga untuk mendapatkan assembly lengkap.
zezba9000
7

Solusi universal ini untuk orang-orang yang perlu memuat tipe generik dari referensi eksternal dinamis dengan AssemblyQualifiedName, tanpa mengetahui dari assembly mana semua bagian dari tipe generik berasal dari:

    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;
    }

Dan Anda dapat mengujinya dengan kode ini (aplikasi konsol):

    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();
    }

Saya membagikan solusi saya untuk membantu orang-orang dengan masalah yang sama dengan saya (untuk menghilangkan semua jenis string yang dapat didefinisikan baik sebagian atau secara keseluruhan dalam rakitan yang direferensikan secara eksternal - dan referensi secara dinamis ditambahkan oleh pengguna aplikasi).

Semoga bisa membantu siapa saja!

PW
sumber
2

Mirip dengan OP, saya perlu memuat subset jenis terbatas berdasarkan nama (dalam kasus saya semua kelas berada dalam satu rakitan dan menerapkan antarmuka yang sama). Saya memiliki banyak masalah aneh ketika mencoba menggunakan Type.GetType(string)perakitan yang berbeda (bahkan menambahkan AssemblyQualifiedName seperti yang disebutkan di posting lain). Inilah cara saya memecahkan masalah:

Pemakaian:

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

Kode:

    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);
                    }
                }
            }
        }

Jelas Anda bisa mengubah metode CacheTypes untuk memeriksa semua rakitan di AppDomain, atau logika lain yang lebih sesuai dengan kasus penggunaan Anda. Jika kasus penggunaan Anda memungkinkan jenis dimuat dari beberapa ruang nama, Anda mungkin ingin mengubah kunci kamus untuk menggunakan jenis itu FullNamesebagai gantinya. Atau jika tipe Anda tidak mewarisi dari antarmuka umum atau kelas dasar, Anda dapat menghapus <BaseType>dan mengubah metode CacheTypes untuk menggunakan sesuatu seperti.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

EverPresent
sumber
1

Pertama muat rakitan dan kemudian jenisnya. contoh: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (typeName);

azulay7
sumber
0

Bisakah Anda menggunakan salah satu cara standar?

typeof( MyClass );

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

Jika tidak, Anda harus menambahkan informasi ke Type.GetType tentang assembly.

Jerod Houghtelling
sumber
0

Pendekatan singkat dan dinamis menggunakan AssemblyQualifiedNameproperti -

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

Nikmati!

simonbor
sumber
10
Jika Type.GetType ("MyProject.Domain.Model." + MyClassName) gagal, bagaimana bisa membungkusnya dengan panggilan GetType lain mencegahnya?
Kaine
1
Dalam kasus apa pun, Anda dapat membungkusnya dalam blok coba tangkap dengan System.NullReferenceException. Jauh lebih mungkin untuk salah dalam ini - "MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089" lalu di ini - "MyProject.Domain.Model." ...
simonbor
1
@Kaine Saya tidak yakin apa yang dimaksud simonbor, tetapi jika Anda menggunakan GetType (). AssemblyQualifiedName saat MENULIS string, maka Anda tidak perlu khawatir tentang hal itu saat menggunakan string untuk menyelesaikan suatu jenis.
Sergio Porres