Tạo phiên bản kiểu không có hàm tạo mặc định trong C # bằng cách sử dụng phản xạ


97

Lấy lớp sau làm ví dụ:

class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

Sau đó, tôi muốn tạo một phiên bản của loại này bằng cách sử dụng phản chiếu:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

Thông thường điều này sẽ hoạt động, tuy nhiên vì SomeTypechưa xác định một phương thức khởi tạo không tham số, lệnh gọi tới Activator.CreateInstancesẽ ném một kiểu ngoại lệ MissingMethodExceptionvới thông báo " Không có phương thức tạo không tham số nào được xác định cho đối tượng này. " Có cách nào thay thế để vẫn tạo một thể hiện của kiểu này không? Sẽ thật là tệ khi thêm các hàm tạo không tham số vào tất cả các lớp của tôi.


2
FormatterServices.GetUninitializedObjectkhông cho phép tạo chuỗi chưa khởi tạo. Bạn có thể nhận được ngoại lệ: System.ArgumentException: Uninitialized Strings cannot be created.Hãy ghi nhớ điều này.
Bartosz Pierzchlewicz

Cảm ơn bạn đã lưu ý, nhưng tôi đã xử lý các chuỗi và các loại cơ bản riêng biệt.
Aistina

Câu trả lời:


142

Ban đầu tôi đã đăng câu trả lời này ở đây , nhưng đây là bản tái bản vì đây không phải là câu hỏi chính xác nhưng có cùng câu trả lời:

FormatterServices.GetUninitializedObject()sẽ tạo một thể hiện mà không cần gọi một hàm tạo. Tôi đã tìm thấy lớp này bằng cách sử dụng Reflector và tìm hiểu một số lớp nối tiếp .Net cốt lõi.

Tôi đã thử nghiệm nó bằng cách sử dụng mã mẫu bên dưới và có vẻ như nó hoạt động tốt:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}

Tuyệt vời, có vẻ như đó chính xác là những gì tôi cần. Tôi giả sử chưa được khởi tạo có nghĩa là tất cả bộ nhớ của nó sẽ được đặt thành số không? (Tương tự như cách cấu trúc được khởi tạo)
Aistina

Dù giá trị mặc định là gì cho mỗi loại sẽ là giá trị mặc định. Vì vậy, các đối tượng sẽ là null, ints 0, v.v. Tôi nghĩ rằng bất kỳ khởi tạo cấp lớp nào xảy ra, nhưng không có phương thức khởi tạo nào được chạy.
Jason Jackson

14
@JSBangs, Thật tệ là bạn đang tìm ra một câu trả lời hoàn toàn hợp pháp. Nhận xét của bạn và câu trả lời khác không thực sự giải quyết câu hỏi được hỏi. Nếu bạn cảm thấy mình có câu trả lời hay hơn, hãy cung cấp một câu trả lời. Nhưng câu trả lời tôi đã cung cấp nêu bật cách sử dụng một lớp được tài liệu hóa giống như cách các lớp tuần tự hóa khác sử dụng mã này.
Jason Jackson

21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) không phải là không có tài liệu.
Autodidact


72

Sử dụng quá tải này của phương thức CreateInstance:

public static Object CreateInstance(
    Type type,
    params Object[] args
)

Tạo một phiên bản của kiểu được chỉ định bằng cách sử dụng hàm tạo phù hợp nhất với các tham số được chỉ định.

Xem: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx


1
Giải pháp này đơn giản hóa vấn đề. Điều gì sẽ xảy ra nếu tôi không biết loại của mình và tôi đang nói "chỉ cần tạo một đối tượng của Loại trong biến Loại này"?
kamii

23

Khi tôi đánh giá hiệu suất của (T)FormatterServices.GetUninitializedObject(typeof(T))nó chậm hơn. Đồng thời, các biểu thức được biên dịch sẽ cung cấp cho bạn những cải tiến tốc độ tuyệt vời mặc dù chúng chỉ hoạt động đối với các kiểu có hàm tạo mặc định. Tôi đã thực hiện một cách tiếp cận kết hợp:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

Điều này có nghĩa là biểu thức tạo được lưu vào bộ nhớ cache một cách hiệu quả và chỉ phải chịu hình phạt trong lần đầu tiên loại được tải. Sẽ xử lý các loại giá trị một cách hiệu quả.

Gọi nó đi:

MyType me = New<MyType>.Instance();

Lưu ý rằng (T)FormatterServices.GetUninitializedObject(t)sẽ không thành công cho chuỗi. Do đó, xử lý đặc biệt cho chuỗi được áp dụng để trả về chuỗi trống.


1
Thật kỳ lạ khi nhìn vào một dòng mã của ai đó có thể tiết kiệm được một ngày. Cảm ơn ngài! Các lý do về hiệu suất đã đưa tôi đến bài đăng của bạn và thủ thuật đã được thực hiện :) Các lớp FormatterServices và Activator hoạt động kém hiệu quả so với các biểu thức đã biên dịch, thật đáng tiếc khi người ta tìm thấy Activator ở khắp nơi.
jmodrak

@nawfal Về cách xử lý đặc biệt của bạn đối với chuỗi, tôi biết nó sẽ không thành công đối với chuỗi nếu không có cách xử lý đặc biệt này, nhưng tôi chỉ muốn biết: nó có hoạt động với tất cả các kiểu khác không?
Sнаđошƒаӽ

@ Sнаđошƒаӽ rất tiếc là không. Ví dụ đã cho là barebone và .NET có nhiều kiểu loại khác nhau. Ví dụ, hãy xem xét, nếu bạn chuyển một kiểu đại biểu, bạn sẽ cung cấp cho bạn một thể hiện như thế nào? Hoặc nếu constructor ném những gì bạn có thể làm với nó? Nhiều cách khác nhau để xử lý nó. Tôi đã cập nhật câu trả lời này để xử lý nhiều tình huống hơn trong thư viện của mình. Nó không được xuất bản ở bất cứ đâu cho bây giờ.
nawfal,

4

Câu trả lời tốt nhưng không sử dụng được trên khuôn khổ nhỏ gọn dot net. Đây là một giải pháp sẽ hoạt động trên CF.Net ...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}

1
Đây là cách tôi gọi một hàm tạo không mặc định. Tôi không chắc mình có bao giờ muốn tạo một đối tượng mà không gọi bất kỳ phương thức khởi dựng nào hay không.
Rory MacLeod

2
Bạn có thể muốn tạo một đối tượng mà không cần gọi hàm tạo nếu bạn đang viết trình tuần tự tùy chỉnh.
Autodidact

1
Đúng, đó là tình huống sử dụng chính xác mà câu hỏi này dành cho :)
Aistina

1
@Aistina Có lẽ bạn có thể thêm thông tin này vào câu hỏi? Hầu hết mọi người sẽ chống lại việc tạo các đối tượng mà không gọi ctors của họ và sẽ dành thời gian để tranh luận với bạn về điều đó, nhưng trường hợp sử dụng của bạn thực sự biện minh cho điều đó, vì vậy tôi nghĩ nó rất phù hợp với chính câu hỏi.
julealgon
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.