Cách tạo một đối tượng mới từ Loại


748

Người ta có thể không phải lúc nào cũng biết Typevề một đối tượng tại thời gian biên dịch, nhưng có thể cần phải tạo một thể hiện của Type.

Làm thế nào để bạn có được một đối tượng mới từ một Type?

Câu trả lời:


896

Các Activatorlớp trong thư mục gốc Systemnamespace là khá mạnh mẽ.

Có rất nhiều quá tải để truyền tham số cho hàm tạo và như vậy. Kiểm tra các tài liệu tại:

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

hoặc (đường dẫn mới)

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

Dưới đây là một số ví dụ đơn giản:

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

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

21
Rất vui vì cuối cùng đã tìm thấy điều này, nhưng cuộc gọi thứ hai không chính xác, thiếu một trích dẫn và parms bị đảo ngược, nên là: ObjectType dụ = (ObjectType) Activator.CreateInstance ("MyAssugging", "MyNamespace.ObjectType");
kevinc

10
Bạn cần gọi 'Unwrap ()' để có được loại đối tượng thực tế bạn muốn: ConcreteType dụ = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε é И

4
Làm thế nào để ObjectType instancephù hợp với điều kiện của OP "Một người có thể không phải lúc nào cũng biết loại đối tượng tại thời điểm biên dịch"? : P
Martin Schneider

@ MA-Maddin được rồi object instance = Activator.CreateInstance(...);.
BrainSlugs83

1
Bất cứ ai cũng biết làm thế nào để làm điều này trong .NET Core? Phương thức Unwrap không khả dụng trên đối tượng.
Justin

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

Các Activatorlớp học có một biến thể chung mà làm cho điều này một chút dễ dàng hơn:

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

8
@Kevin Tất nhiên rồi. Một hoạt động như vậy không thể hoạt động trong một ngôn ngữ gõ tĩnh bởi vì nó không có ý nghĩa. Bạn không thể gọi các phương thức trên một đối tượng không xác định. Trong khi đó (= kể từ khi văn bản này câu trả lời) C # đã có những dynamiccấu trúc mà không cho phép các cấu trúc như vậy nhưng đối với hầu hết các mục đích câu trả lời này vẫn bao gồm nó.
Konrad Rudolph

1
@KonradRudolph Không hoàn toàn đúng. Đầu tiên của c # không cho phép bạn tạo các kiểu mới khi chạy. Bạn không thể gọi bất cứ thứ gì trên chúng theo cách an toàn tĩnh . Vì vậy, yeah, bạn là một nửa chính xác. Nhưng thực tế hơn, bạn cần điều này khi bạn tải các cụm khi chạy, điều đó có nghĩa là loại không được biết đến trong thời gian biên dịch. C # sẽ bị hạn chế nghiêm trọng nếu bạn không thể làm điều này. Ý tôi là bạn tự chứng minh điều đó: làm thế nào khác phương thức Activator có một thể hiện kiểu hoạt động? Khi MS viết lớp Activator, họ không có kiến ​​thức về thời gian biên dịch về bất kỳ loại nào trong tương lai mà người dùng sẽ viết.
AnorZaken

1
@AnorZaken Nhận xét của tôi không nói gì về việc tạo kiểu khi chạy. Tất nhiên bạn có thể làm điều đó, nhưng bạn không thể sử dụng chúng một cách tĩnh trong cùng một bối cảnh (tất nhiên bạn có thể lưu trữ một chương trình được biên dịch tĩnh hoàn toàn). Đó là tất cả nhận xét của tôi đang nói.
Konrad Rudolph

@KonradRudolph Ah xin lỗi, giải thích sai "một hoạt động như vậy" có nghĩa là bắt đầu một loại chỉ được biết đến trong thời gian chạy; thay vì ý nghĩa bằng cách sử dụng kiểu thời gian chạy làm tham số kiểu chung.
AnorZaken

1
@AnorZaken - Về mặt kỹ thuật, bạn có thể vừa tạo các loại mới trong thời gian chạy VÀ gọi các phương thức trên chúng theo cách an toàn tĩnh nếu loại mới của bạn thực hiện một giao diện đã biết hoặc kế thừa một lớp cơ sở đã biết. - Một trong những cách tiếp cận đó sẽ cung cấp cho bạn một hợp đồng tĩnh cho đối tượng được tạo trong thời gian chạy của bạn.
BrainSlugs83

132

Biểu hiện tổng hợp là cách tốt nhất! (để thực hiện nhiều lần tạo cá thể trong thời gian chạy).

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

X x = YCreator();

Thống kê (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

Thống kê (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

Thống kê (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

Thống kê (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

Thống kê (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

Thống kê (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

Mã đầy đủ:

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 cho tất cả các số liệu thống kê! Tôi không thực sự cần loại hiệu suất này vào lúc này, nhưng vẫn rất thú vị. :)
AnorZaken

1
Ngoài ra còn có TypeDescriptor.CreateInstance (xem stackoverflow.com/a/17797389/1242 ) có thể nhanh hơn nếu được sử dụng với TypeDescriptor.AddProvider
Lars Truijens

2
Điều này có còn hữu ích khi bạn không biết loại nào Xđang chạy không?
ajeh

1
@ajeh Vâng. Thay đổi typeof (T) thành Type.GetType (..).
Serj-Tm

3
@ Serj-Tm Không, sẽ không hoạt động nếu loại X là thời gian chạy Type.
NetMage

47

Một cách thực hiện của vấn đề này là cố gắng gọi hàm tạo không tham số của Kiểu:

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

Đây là cách tiếp cận tương tự, được chứa trong một phương thức chung:

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

15
Lập trình hướng ngoại lệ? Điều này có vẻ như là một triển khai rất kém khi bạn chỉ có thể phản ánh qua loại để xác định các hàm tạo.
Firoso

16

Nó khá đơn giản. Giả sử rằng tên lớp của bạn là Carvà không gian tên là Vehicles, sau đó truyền tham số như Vehicles.Cartrả về đối tượng kiểu Car. Như thế này, bạn có thể tạo bất kỳ trường hợp nào của bất kỳ lớp nào một cách linh hoạt.

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

Nếu Tên đủ điều kiện của bạn (nghĩa là, Vehicles.Cartrong trường hợp này) nằm trong một hội đồng khác, thì Type.GetTypenó sẽ là null. Trong những trường hợp như vậy, bạn có vòng lặp thông qua tất cả các hội đồng và tìm Type. Cho rằng bạn có thể sử dụng mã dưới đây

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

Và bạn có thể lấy ví dụ bằng cách gọi phương thức trên.

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

Trong trường hợp thứ hai của bạn (lắp ráp bên ngoài), bạn có thể chỉ cần chuyển "Phương tiện. Khác, Cài đặt khác" cho phương thức đầu tiên của mình và nó sẽ hoạt động. Rõ ràng OtherAssugging là tên của hội đồng mà nó sống.
danmiser

2
@danmiser Điều đó cần mã hóa cứng tên lắp ráp. Để thực hiện tính linh hoạt, tôi đang kiểm tra null và mã hoạt động theo cách động :)
Sarath Avanavu

14

Nếu điều này là cho một cái gì đó sẽ được gọi là rất nhiều trong một ví dụ ứng dụng, thì việc biên dịch và lưu trữ mã động bộ đệm thay vì sử dụng trình kích hoạt hoặc nhanh hơn rất nhiều ConstructorInfo.Invoke(). Hai tùy chọn dễ dàng để biên dịch động được biên dịch Linq Expressions hoặc một số ILopcodesDynamicMethod đơn giản . Dù bằng cách nào, sự khác biệt là rất lớn khi bạn bắt đầu vào các vòng lặp chặt chẽ hoặc nhiều cuộc gọi.


11

Sẽ không phải là T t = new T();công việc chung ?


9
Trên thực tế, nó sẽ trong một lớp / phương thức chung, nhưng không phải cho một "Loại" nhất định.
Brady Moritz

Giả sử rằng loại T có ràng buộc 'new ()'.
Rob Von Nesselrode

10

Nếu bạn muốn sử dụng hàm tạo mặc định thì giải pháp sử dụng System.Activatorđược trình bày trước đó có lẽ là thuận tiện nhất. Tuy nhiên, nếu loại thiếu một hàm tạo mặc định hoặc bạn phải sử dụng một hàm không mặc định, thì một tùy chọn là sử dụng sự phản chiếu hoặc System.ComponentModel.TypeDescriptor. Trong trường hợp phản xạ, chỉ cần biết tên loại (với không gian tên của nó là đủ).

Ví dụ sử dụng sự phản chiếu:

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

Ví dụ sử dụng 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[]chính xác là những gì tôi đến câu hỏi này để tìm, cảm ơn!
Chad

10

Không sử dụng Reflection:

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

5
Điều này hữu ích như thế nào? Bạn phải biết loại đã gọi phương thức đó và nếu bạn biết loại bạn có thể xây dựng nó mà không cần một phương thức đặc biệt.
Kyle Delaney

Vì vậy, T có thể thay đổi trong thời gian chạy. Hữu ích nếu bạn làm việc với các loại bị biến dạng.

một T () mới; sẽ thất bại nếu T không phải là kiểu tham chiếu với hàm tạo không tham số, Phương thức này sử dụng các điều khoản để đảm bảo T là kiểu tham chiếu và có hàm tạo.

3
Làm thế nào T có thể thay đổi trong thời gian chạy? Bạn không cần phải biết T tại thời điểm thiết kế để gọi Tạo <>?
Kyle Delaney

Nếu bạn làm việc với các lớp và giao diện chung trong các nhà máy, các loại thực hiện giao diện nên được cung cấp có thể thay đổi.

8

Do vấn đề này, Activator sẽ hoạt động khi có một ctor không tham số. Nếu đây là một hạn chế xem xét sử dụng

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()

5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}

4

Tôi có thể vượt qua câu hỏi này bởi vì tôi đang tìm cách triển khai một phương thức CloneObject đơn giản cho lớp tùy ý (với một hàm tạo mặc định)

Với phương thức chung, bạn có thể yêu cầu kiểu thực hiện New ().

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

Với giả định không chung chung, kiểu này có hàm tạo mặc định và bắt ngoại lệ nếu không.

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
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.