Giải quyết loại từ tên lớp trong một hội khác


87

Tôi có một phương thức mà tôi cần giải quyết Loại của một lớp. Lớp này tồn tại trong một assembly khác với không gian tên tương tự như:

MyProject.Domain.Model

Tôi đang cố gắng thực hiện những điều sau:

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

Điều này hoạt động tốt nếu mã đang thực hiện hành động này nằm trong cùng một assembly với lớp có kiểu mà tôi đang cố gắng giải quyết, tuy nhiên, nếu lớp của tôi nằm trong một assembly khác, thì mã này không thành công.

Tôi chắc chắn có một cách tốt hơn nhiều để hoàn thành nhiệm vụ này, nhưng tôi chưa có nhiều kinh nghiệm về việc giải quyết các tập hợp và duyệt qua các không gian tên bên trong để giải quyết loại lớp mà tôi đang tìm kiếm. Bất kỳ lời khuyên hoặc mẹo để hoàn thành nhiệm vụ này một cách duyên dáng hơn?


Câu trả lời:


171

Bạn sẽ phải thêm tên lắp ráp như sau:

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

Để tránh mơ hồ hoặc nếu lắp ráp nằm trong GAC, bạn nên cung cấp tên lắp ráp đủ điều kiện như sau:

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

Tuyệt vời, tôi biết tôi đã thiếu một cái gì đó nhỏ như lắp ráp. Giải pháp này phù hợp với nhu cầu của tôi. Cảm ơn.
Brandon

10
Và đối với những giao dịch trong tuần tự: Để có được tên lắp ráp có trình độ, có tài sản Type.AssemblyQualifiedName
Michael hoang dã

1
Nếu kiểu là Danh sách <T>, trong đó T là lớp tùy chỉnh, làm cách nào bạn chỉ định 2 hợp ngữ? Tức là hợp ngữ mscorlib cho System.Collections.Generic.List và thư viện chứa T?
Simon Green

@SimonGreen: Bạn có thể phải xây dựng nó bằng cách sử dụng listType.MakeGenericType(itemType). Cả hai biến kiểu đều có thể được xây dựng bằng cách sử dụng Type.GetType()như trong câu trả lời của tôi.
Sandor Drieënhuizen

object.Assembly.ToString () Cũng có thể được sử dụng để lấy toàn bộ assembly.
zezba9000

6

Giải pháp phổ biến này là dành cho những người cần để nạp kiểu generic từ tài liệu tham khảo động bên ngoài bằng cách AssemblyQualifiedName, mà không biết từ đó lắp ráp được tất cả các phần của kiểu generic đến từ:

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

bạn có thể kiểm tra nó bằng mã này (ứng dụng bảng điều khiển):

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

Tôi đang chia sẻ giải pháp của mình để giúp những người có cùng vấn đề như tôi (giải mã hóa BẤT KỲ loại nào từ chuỗi có thể được xác định cả một phần hoặc toàn bộ trong assembly được tham chiếu bên ngoài - và các tham chiếu được thêm động bởi người dùng ứng dụng).

Hy vọng nó sẽ giúp bất cứ ai!


2

Tương tự như OP, tôi cần tải một tập hợp con giới hạn các kiểu theo tên (trong trường hợp của tôi, tất cả các lớp đều nằm trong một assembly duy nhất và được triển khai cùng một giao diện). Tôi đã gặp rất nhiều vấn đề kỳ lạ khi cố gắng sử dụng Type.GetType(string)với một hội đồng khác (thậm chí thêm AssemblyQualifiedName như đã đề cập trong các bài viết khác). Đây là cách tôi giải quyết vấn đề:

Sử dụng:

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

Mã:

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

Rõ ràng là bạn có thể tinh chỉnh phương thức CacheTypes để kiểm tra tất cả các tập hợp trong AppDomain hoặc logic khác phù hợp hơn với trường hợp sử dụng của bạn. Nếu trường hợp sử dụng của bạn cho phép các loại được tải từ nhiều không gian tên, bạn có thể muốn thay đổi khóa từ điển để sử dụng loại FullNamethay thế. Hoặc nếu các kiểu của bạn không kế thừa từ một giao diện chung hoặc lớp cơ sở, bạn có thể xóa <BaseType>và thay đổi phương thức CacheTypes để sử dụng một cái gì đó như.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")


1

Đầu tiên tải lắp ráp và sau đó là loại. ví dụ: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (typeName);


0

Bạn có thể sử dụng một trong hai cách tiêu chuẩn không?

typeof( MyClass );

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

Nếu không, bạn sẽ phải thêm thông tin vào Type.GetType về lắp ráp.


0

Cách tiếp cận ngắn và năng động bằng cách sử dụng thuộc AssemblyQualifiedNametính -

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

Thưởng thức!


10
Nếu Type.GetType ("MyProject.Domain.Model." + MyClassName) không thành công, làm thế nào để gói nó trong một lệnh gọi GetType khác ngăn chặn điều đó?
Kaine

1
Trong mọi trường hợp, bạn có thể bọc nó trong một khối try catch với System.NullReferenceException. Nhiều khả năng bị nhầm lẫn trong điều này - "MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = trung lập, PublicKeyToken = b77a5c561934e089" thì trong này - "MyProject.Domain.Model." ...
simonbor

1
@Kaine Tôi không chắc simonbor có nghĩa là gì, nhưng nếu bạn sử dụng GetType (). AssemblyQualifiedName khi VIẾT chuỗi, thì bạn không phải lo lắng về điều đó khi sử dụng chuỗi để phân giải thành một kiểu.
Sergio Porres
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.