แก้ไขประเภทจากชื่อคลาสในแอสเซมบลีที่แตกต่างกัน


87

ฉันมีวิธีการที่ฉันต้องการแก้ไขประเภทของคลาส คลาสนี้มีอยู่ในแอสเซมบลีอื่นที่มีเนมสเปซคล้ายกับ:

MyProject.Domain.Model

ฉันกำลังพยายามดำเนินการดังต่อไปนี้:

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

วิธีนี้ใช้ได้ผลดีหากโค้ดที่กำลังดำเนินการนี้อยู่ในแอสเซมบลีเดียวกันกับคลาสที่ฉันกำลังพยายามแก้ไขอย่างไรก็ตามหากคลาสของฉันอยู่ในแอสเซมบลีอื่นรหัสนี้จะล้มเหลว

ฉันแน่ใจว่ามีวิธีที่ดีกว่าในการทำงานนี้ให้สำเร็จ แต่ฉันไม่มีประสบการณ์มากนักในการแก้ไขแอสเซมบลีและการสำรวจเนมสเปซภายในเพื่อแก้ไขประเภทของคลาสที่ฉันกำลังมองหา คำแนะนำหรือเคล็ดลับในการทำงานนี้ให้สำเร็จลุล่วงอย่างสง่างามมากขึ้น?


อาจซ้ำกันได้ของType.GetType ("namespace.abClassName") คืนค่า null

คำตอบ:


171

คุณจะต้องเพิ่มชื่อแอสเซมบลีดังนี้:

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

เพื่อหลีกเลี่ยงความคลุมเครือหรือหากชุดประกอบอยู่ใน GAC คุณควรระบุชื่อแอสเซมบลีที่มีคุณสมบัติครบถ้วนดังนี้:

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

ยอดเยี่ยมฉันรู้ว่าฉันขาดบางสิ่งเล็กน้อยเช่นรวมถึงการประกอบ โซลูชันนี้เหมาะกับความต้องการของฉัน ขอบคุณ.
Brandon

11
และสำหรับผู้ที่เกี่ยวข้องในการทำให้เป็นอนุกรม: เพื่อให้ได้ชื่อที่ผ่านการรับรองจากแอสเซมบลีจะมีคุณสมบัติType.AssemblyQualifiedName
Michael Wild

1
ถ้า type เป็น List <T> โดยที่ T คือคลาสแบบกำหนดเองคุณจะระบุ 2 แอสเซมบลีได้อย่างไร? ได้แก่ . ชุดประกอบ mscorlib สำหรับ System.Collections.Generic.List และไลบรารีที่มี T?
Simon Green

@SimonGreen: คุณอาจจะต้องสร้างโดยใช้listType.MakeGenericType(itemType). ตัวแปรประเภททั้งสองสามารถสร้างได้โดยใช้Type.GetType()like ในคำตอบของฉัน
Sandor Drieënhuizen

object.Assembly ToString () สามารถใช้เพื่อรับชุดประกอบทั้งหมด
zezba9000

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

ก่อนอื่นให้ใส่ชุดประกอบจากนั้นจึงพิมพ์ เช่น Assembly 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

1
ไม่ว่าในกรณีใดคุณสามารถรวมไว้ในบล็อก try catch ด้วย System.NullReferenceException มีแนวโน้มที่จะเข้าใจผิดมากขึ้นในสิ่งนี้ - "MyProject.Domain.Model.ClassName, ClassName, เวอร์ชัน = 2.0.0.0, วัฒนธรรม = เป็นกลาง, PublicKeyToken = b77a5c561934e089" ตามนี้ - "MyProject.Domain.Model." ...
simonbor

1
@Kaine ฉันไม่แน่ใจว่า simonbor หมายถึงอะไร แต่ถ้าคุณใช้ GetType () AssemblyQualifiedName เมื่อเขียนสตริงคุณก็ไม่ต้องกังวลเกี่ยวกับมันเมื่อใช้สตริงเพื่อแก้ไขเป็นประเภท
Sergio Porres
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.