วิธีสร้างอินสแตนซ์วัตถุใหม่จากประเภท


748

หนึ่งอาจจะไม่เคยรู้Typeของวัตถุที่รวบรวมเวลา Typeแต่อาจจำเป็นต้องสร้างอินสแตนซ์ที่

คุณจะรับอินสแตนซ์ของวัตถุใหม่ได้Typeอย่างไร

คำตอบ:


896

ActivatorระดับภายในรากSystemnamespace มีประสิทธิภาพสวย

มีจำนวนมากเกินพิกัดสำหรับการส่งพารามิเตอร์ไปยังตัวสร้างและเช่น ตรวจสอบเอกสารที่:

http://msdn.microsoft.com/en-us/library/system.activator.createinstance.aspx

หรือ (เส้นทางใหม่)

https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance

นี่คือตัวอย่างง่ายๆ:

ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");

21
ดีใจที่ได้พบสิ่งนี้ในที่สุด แต่การโทรครั้งที่สองไม่ถูกต้องพลาดเครื่องหมายคำพูดและ parms ที่กลับด้านควรจะเป็น: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
kevinc

10
คุณต้องเรียก 'Unwrap ()' เพื่อรับชนิดของวัตถุที่คุณต้องการ: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType). Unwrap ();
ΕГИІИО

4
วิธีการที่ไม่ObjectType instanceตรงกับสภาพของ OP "หนึ่งอาจไม่ทราบชนิดของวัตถุที่รวบรวมเวลา" หรือไม่? : P
Martin Schneider

@ MA-Maddin object instance = Activator.CreateInstance(...);โอเคแล้ว
BrainSlugs83

1
ใครรู้วิธีการทำใน. NET Core? กระบวนการแกะวิธีไม่พร้อมใช้งานบนวัตถุ
Justin

145
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

Activatorชั้นจะมีตัวแปรทั่วไปที่ทำให้เรื่องนี้ง่ายขึ้นเล็กน้อย:

ObjectType instance = Activator.CreateInstance<ObjectType>();

8
@ เควินแน่นอน การดำเนินการดังกล่าวไม่สามารถทำงานในภาษาที่พิมพ์แบบคงที่ได้เนื่องจากไม่เหมาะสม คุณไม่สามารถเรียกใช้เมธอดบนวัตถุประเภทที่ไม่รู้จัก ในขณะเดียวกัน (= ตั้งแต่การเขียนคำตอบนี้) C # ได้มีdynamicโครงสร้างซึ่งจะช่วยให้โครงสร้างดังกล่าว แต่สำหรับวัตถุประสงค์มากที่สุดคำตอบนี้ยังครอบคลุม
Konrad Rudolph

1
@ KonradRudolph ไม่จริงเลย ครั้งแรกของ C # จะช่วยให้คุณสามารถสร้างรูปแบบใหม่ที่รันไทม์ คุณก็ไม่สามารถเรียกร้องอะไรกับพวกเขาในวิธีที่ปลอดภัยแบบคงที่ ดังนั้นใช่คุณถูกต้องครึ่งหนึ่ง แต่ยิ่งแนบเนียนเมื่อคุณโหลดแอสเซมบลีที่รันไทม์ซึ่งหมายความว่าไม่รู้จักประเภทที่รวบรวมเวลา C # จะถูก จำกัด อย่างรุนแรงหากคุณไม่สามารถทำได้ ฉันหมายความว่าคุณเพิ่งพิสูจน์ด้วยตัวคุณเอง: วิธี Activator นั้นใช้งานอินสแตนซ์ของประเภทอื่นได้อย่างไร เมื่อ MS เขียนคลาส Activator พวกเขาไม่มีความรู้ในการรวบรวมเกี่ยวกับอนาคตที่ผู้ใช้ประเภทใด ๆ จะเขียน
AnorZaken

1
@AnorZaken ความคิดเห็นของฉันไม่เกี่ยวกับการสร้างประเภทที่ runtime แน่นอนว่าคุณสามารถทำเช่นนั้นได้ แต่คุณไม่สามารถใช้พวกมันแบบสแตติกในบริบทเดียวกันได้ (คุณสามารถโฮสต์โปรแกรมที่รวบรวมแบบเต็มของหลักสูตร) นั่นคือทั้งหมดที่ความคิดเห็นของฉันกำลังพูด
Konrad Rudolph

@ KonradRudolph อ่าขออภัยตีความ "การดำเนินการเช่นนี้" เพื่อแปลความหมายของอินสแตนซ์ที่เป็นที่รู้จักกันเฉพาะตอนรันไทม์ แทนความหมายโดยใช้ชนิดรันไทม์เป็นพารามิเตอร์ประเภททั่วไป
AnorZaken

1
@AnorZaken - ในทางเทคนิคคุณสามารถสร้างชนิดใหม่ที่รันไทม์และวิธีการโทรในรูปแบบที่ปลอดภัยแบบคงที่หากประเภทใหม่ของคุณใช้อินเทอร์เฟซที่รู้จักหรือสืบทอดคลาสฐานที่รู้จัก - วิธีใดวิธีหนึ่งเหล่านี้จะให้สัญญาแบบคงที่สำหรับวัตถุที่สร้างขึ้นแบบรันไทม์ของคุณ
BrainSlugs83

132

รวบรวมการแสดงออกเป็นวิธีที่ดีที่สุด! (เพื่อประสิทธิภาพในการสร้างอินสแตนซ์ในรันไทม์ซ้ำ ๆ )

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();

X x = YCreator();

สถิติ (2012):

    Iterations: 5000000
    00:00:00.8481762, Activator.CreateInstance(string, string)
    00:00:00.8416930, Activator.CreateInstance(type)
    00:00:06.6236752, ConstructorInfo.Invoke
    00:00:00.1776255, Compiled expression
    00:00:00.0462197, new

สถิติ (2015, .net 4.5, x64):

    Iterations: 5000000
    00:00:00.2659981, Activator.CreateInstance(string, string)
    00:00:00.2603770, Activator.CreateInstance(type)
    00:00:00.7478936, ConstructorInfo.Invoke
    00:00:00.0700757, Compiled expression
    00:00:00.0286710, new

สถิติ (2015, .net 4.5, x86):

    Iterations: 5000000
    00:00:00.3541501, Activator.CreateInstance(string, string)
    00:00:00.3686861, Activator.CreateInstance(type)
    00:00:00.9492354, ConstructorInfo.Invoke
    00:00:00.0719072, Compiled expression
    00:00:00.0229387, new

สถิติ (2017, LINQPad 5.22.02 / x64 / .NET 4.6):

    Iterations: 5000000
    No args
    00:00:00.3897563, Activator.CreateInstance(string assemblyName, string typeName)
    00:00:00.3500748, Activator.CreateInstance(Type type)
    00:00:01.0100714, ConstructorInfo.Invoke
    00:00:00.1375767, Compiled expression
    00:00:00.1337920, Compiled expression (type)
    00:00:00.0593664, new
    Single arg
    00:00:03.9300630, Activator.CreateInstance(Type type)
    00:00:01.3881770, ConstructorInfo.Invoke
    00:00:00.1425534, Compiled expression
    00:00:00.0717409, new

สถิติ (2019, x64 / .NET 4.8):

Iterations: 5000000
No args
00:00:00.3287835, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.3122015, Activator.CreateInstance(Type type)
00:00:00.8035712, ConstructorInfo.Invoke
00:00:00.0692854, Compiled expression
00:00:00.0662223, Compiled expression (type)
00:00:00.0337862, new
Single arg
00:00:03.8081959, Activator.CreateInstance(Type type)
00:00:01.2507642, ConstructorInfo.Invoke
00:00:00.0671756, Compiled expression
00:00:00.0301489, new

สถิติ (2019, x64 / .NET Core 3.0):

Iterations: 5000000
No args
00:00:00.3226895, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.2786803, Activator.CreateInstance(Type type)
00:00:00.6183554, ConstructorInfo.Invoke
00:00:00.0483217, Compiled expression
00:00:00.0485119, Compiled expression (type)
00:00:00.0434534, new
Single arg
00:00:03.4389401, Activator.CreateInstance(Type type)
00:00:01.0803609, ConstructorInfo.Invoke
00:00:00.0554756, Compiled expression
00:00:00.0462232, new

รหัสเต็ม:

static X CreateY_New()
{
    return new Y();
}

static X CreateY_New_Arg(int z)
{
    return new Y(z);
}

static X CreateY_CreateInstance()
{
    return (X)Activator.CreateInstance(typeof(Y));
}

static X CreateY_CreateInstance_String()
{
    return (X)Activator.CreateInstance("Program", "Y").Unwrap();
}

static X CreateY_CreateInstance_Arg(int z)
{
    return (X)Activator.CreateInstance(typeof(Y), new object[] { z, });
}

private static readonly System.Reflection.ConstructorInfo YConstructor =
    typeof(Y).GetConstructor(Type.EmptyTypes);
private static readonly object[] Empty = new object[] { };
static X CreateY_Invoke()
{
    return (X)YConstructor.Invoke(Empty);
}

private static readonly System.Reflection.ConstructorInfo YConstructor_Arg =
    typeof(Y).GetConstructor(new[] { typeof(int), });
static X CreateY_Invoke_Arg(int z)
{
    return (X)YConstructor_Arg.Invoke(new object[] { z, });
}

private static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
).Compile();
static X CreateY_CompiledExpression()
{
    return YCreator();
}

private static readonly Func<X> YCreator_Type = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y))
).Compile();
static X CreateY_CompiledExpression_Type()
{
    return YCreator_Type();
}

private static readonly ParameterExpression YCreator_Arg_Param = Expression.Parameter(typeof(int), "z");
private static readonly Func<int, X> YCreator_Arg = Expression.Lambda<Func<int, X>>(
   Expression.New(typeof(Y).GetConstructor(new[] { typeof(int), }), new[] { YCreator_Arg_Param, }),
   YCreator_Arg_Param
).Compile();
static X CreateY_CompiledExpression_Arg(int z)
{
    return YCreator_Arg(z);
}

static void Main(string[] args)
{
    const int iterations = 5000000;

    Console.WriteLine("Iterations: {0}", iterations);

    Console.WriteLine("No args");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(string assemblyName, string typeName)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<X>)CreateY_Invoke},
        new {Name = "Compiled expression", Creator = (Func<X>)CreateY_CompiledExpression},
        new {Name = "Compiled expression (type)", Creator = (Func<X>)CreateY_CompiledExpression_Type},
        new {Name = "new", Creator = (Func<X>)CreateY_New},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator().Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator();
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }

    Console.WriteLine("Single arg");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<int, X>)CreateY_CreateInstance_Arg},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<int, X>)CreateY_Invoke_Arg},
        new {Name = "Compiled expression", Creator = (Func<int, X>)CreateY_CompiledExpression_Arg},
        new {Name = "new", Creator = (Func<int, X>)CreateY_New_Arg},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator(i).Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator(i);
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }
}

public class X
{
  public X() { }
  public X(int z) { this.Z = z; }
  public int Z;
}

public class Y : X
{
    public Y() {}
    public Y(int z) : base(z) {}
}

18
+1 สำหรับสถิติทั้งหมด! ฉันไม่ต้องการการแสดงประเภทนี้ในขณะนี้ แต่ก็น่าสนใจมาก :)
AnorZaken

1
นอกจากนี้ยังมี TypeDescriptor.CreateInstance (ดูstackoverflow.com/a/17797389/1242 ) ซึ่งสามารถเร็วขึ้นหากใช้กับ TypeDescriptor.AddProvider
Lars Truijens

2
สิ่งนี้ยังคงมีประโยชน์เมื่อคุณไม่ทราบชนิดของXรันไทม์หรือไม่
ajeh

1
@ajeh ใช่ เปลี่ยน typeof (T) เป็น Type.GetType (.. )
Serj-Tm

3
@ Serj-TM ไม่มีที่จะไม่ทำงานหากประเภท X Typeเป็นรันไทม์
NetMage

47

การดำเนินการอย่างหนึ่งของปัญหานี้คือการพยายามเรียกตัวสร้างพารามิเตอร์ที่ไม่ใช้ประเภท:

public static object GetNewObject(Type t)
{
    try
    {
        return t.GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return null;
    }
}

นี่คือวิธีการเดียวกันที่มีอยู่ในวิธีการทั่วไป:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}

15
การเขียนโปรแกรมขับเคลื่อนยกเว้น? สิ่งนี้ดูเหมือนว่าการติดตั้งที่แย่มากเมื่อคุณสามารถไตร่ตรองประเภทเพื่อกำหนดตัวสร้างได้
Firoso

16

มันค่อนข้างง่าย สมมติว่า ClassName ของคุณCarและ namespace จะVehiclesแล้วผ่านพารามิเตอร์ซึ่งผลตอบแทนวัตถุประเภทVehicles.Car Carเช่นนี้คุณสามารถสร้างตัวอย่างของคลาสใดก็ได้แบบไดนามิก

public object GetInstance(string strNamesapace)
{         
     Type t = Type.GetType(strNamesapace); 
     return  Activator.CreateInstance(t);         
}

หากชื่อที่ผ่านการรับรองของคุณ(เช่นVehicles.Carในกรณีนี้) อยู่ในการชุมนุมอื่นType.GetTypeจะเป็นโมฆะ Typeในกรณีเช่นนี้คุณมีห่วงผ่านการประกอบและพบว่า เพื่อที่คุณสามารถใช้รหัสด้านล่าง

public object GetInstance(string strFullyQualifiedName)
{
     Type type = Type.GetType(strFullyQualifiedName);
     if (type != null)
         return Activator.CreateInstance(type);
     foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
     {
         type = asm.GetType(strFullyQualifiedName);
         if (type != null)
             return Activator.CreateInstance(type);
     }
     return null;
 }

และคุณสามารถรับอินสแตนซ์ได้โดยการเรียกใช้วิธีการด้านบน

object objClassInstance = GetInstance("Vehicles.Car");

ในกรณีที่สองของคุณ (แอสเซมบลีภายนอก) คุณสามารถส่งผ่านใน "Vehicles.Car, OtherAssembly" ไปยังวิธีแรกของคุณและมันจะทำงาน เห็นได้ชัดว่าชุดประกอบอื่น ๆ เป็นชื่อของชุดประกอบที่ใช้งานอยู่
danmiser

2
@danmiser ที่ต้องการการเข้ารหัสชื่อชุดประกอบอย่างหนัก เพื่อที่จะใช้ความยืดหยุ่นฉันกำลังตรวจสอบโมฆะและรหัสการทำงานในลักษณะแบบไดนามิก :)
Sarath Avanavu

14

หากนี่เป็นสิ่งที่จะถูกเรียกว่าเป็นจำนวนมากในกรณีโปรแกรมมันเร็วมากเพื่อรวบรวมและรหัสแคชแบบไดนามิกแทนการใช้ Activator ConstructorInfo.Invoke()หรือ สองตัวเลือกที่ง่ายสำหรับการรวบรวมแบบไดนามิกเป็นข้อมูลที่รวบรวมLinq นิพจน์หรือบางอย่างง่ายILopcodes DynamicMethodและ ความแตกต่างนั้นมีขนาดใหญ่มากเมื่อคุณเริ่มมีปัญหากับลูปหรือการโทรหลายสาย



10

หากคุณต้องการใช้คอนสตรัคเตอร์เริ่มต้นวิธีแก้ปัญหาที่System.Activatorนำเสนอก่อนหน้านี้น่าจะสะดวกที่สุด อย่างไรก็ตามถ้าชนิดนั้นไม่มีตัวสร้างเริ่มต้นหรือคุณต้องใช้ตัวที่ไม่ใช่ค่าเริ่มต้นตัวเลือกคือใช้การสะท้อนหรือSystem.ComponentModel.TypeDescriptorแต่ถ้าประเภทขาดสร้างเริ่มต้นหรือคุณต้องใช้อย่างใดอย่างหนึ่งที่ไม่ได้เริ่มต้นแล้วตัวเลือกคือการใช้การสะท้อนหรือในกรณีของการสะท้อนมันก็พอที่จะรู้เพียงแค่ชื่อประเภท (ที่มีชื่อของมัน)

ตัวอย่างการใช้การไตร่ตรอง:

ObjectType instance = 
    (ObjectType)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(
        typeName: objectType.FulName, // string including namespace of the type
        ignoreCase: false,
        bindingAttr: BindingFlags.Default,
        binder: null,  // use default binder
        args: new object[] { args, to, constructor },
        culture: null, // use CultureInfo from current thread
        activationAttributes: null
    );

ตัวอย่างการใช้TypeDescriptor:

ObjectType instance = 
    (ObjectType)System.ComponentModel.TypeDescriptor.CreateInstance(
        provider: null, // use standard type description provider, which uses reflection
        objectType: objectType,
        argTypes: new Type[] { types, of, args },
        args: new object[] { args, to, constructor }
    );

args[]เป็นสิ่งที่ฉันมาถึงคำถามนี้เพื่อค้นหาขอบคุณ!
ชาด

10

โดยไม่ต้องใช้ Reflection:

private T Create<T>() where T : class, new()
{
    return new T();
}

5
สิ่งนี้มีประโยชน์อย่างไร? คุณต้องรู้ประเภทที่จะเรียกวิธีการนั้นแล้วและถ้าคุณรู้ว่าประเภทที่คุณสามารถสร้างได้โดยไม่ต้องใช้วิธีพิเศษ
Kyle Delaney

ดังนั้น T สามารถแตกต่างกันที่รันไทม์ มีประโยชน์ถ้าคุณทำงานกับประเภทที่ได้รับการยกเว้น

T ใหม่ (); จะล้มเหลวหาก T ไม่ใช่ประเภทการอ้างอิงที่มีตัวสร้างแบบไม่มีพารามิเตอร์วิธีนี้ใช้ contraints เพื่อให้แน่ใจว่า T เป็นประเภทการอ้างอิงและมี Constructor

3
T แตกต่างกันอย่างไรในขณะใช้งานจริง? คุณไม่ต้องรู้จัก T ในเวลาออกแบบเพื่อที่จะเรียกใช้สร้าง <>?
Kyle Delaney

ถ้าคุณทำงานกับคลาสทั่วไปและอินเทอร์เฟซในโรงงานชนิดที่ใช้อินเทอร์เฟซที่ควรได้รับการ instanciated อาจแตกต่างกันไป

8

เมื่อได้รับปัญหานี้ Activator จะทำงานเมื่อไม่มี ctor ที่ไม่มีพารามิเตอร์ หากนี่เป็นข้อ จำกัด ให้พิจารณาใช้

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()


4

ฉันสามารถข้ามคำถามนี้เพราะฉันต้องการใช้วิธีการ CloneObject ง่าย ๆ สำหรับชั้นเรียนโดยพลการ (กับตัวสร้างเริ่มต้น)

ด้วยวิธีการทั่วไปคุณสามารถกำหนดให้ประเภทดำเนินการใหม่ ()

Public Function CloneObject(Of T As New)(ByVal src As T) As T
    Dim result As T = Nothing
    Dim cloneable = TryCast(src, ICloneable)
    If cloneable IsNot Nothing Then
        result = cloneable.Clone()
    Else
        result = New T
        CopySimpleProperties(src, result, Nothing, "clone")
    End If
    Return result
End Function

หากไม่ใช่แบบทั่วไปสมมติว่าชนิดนั้นมีตัวสร้างค่าเริ่มต้นและตรวจจับข้อยกเว้นหากไม่มี

Public Function CloneObject(ByVal src As Object) As Object
    Dim result As Object = Nothing
    Dim cloneable As ICloneable
    Try
        cloneable = TryCast(src, ICloneable)
        If cloneable IsNot Nothing Then
            result = cloneable.Clone()
        Else
            result = Activator.CreateInstance(src.GetType())
            CopySimpleProperties(src, result, Nothing, "clone")
        End If
    Catch ex As Exception
        Trace.WriteLine("!!! CloneObject(): " & ex.Message)
    End Try
    Return result
End Function
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.