Truyền một hệ thống tức thời. Nhập vào dưới dạng tham số loại cho lớp chung


182

Tiêu đề là loại tối nghĩa. Những gì tôi muốn biết là nếu điều này là có thể:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

Rõ ràng, MyGenericClass được mô tả là:

public class MyGenericClass<T>

Ngay bây giờ, trình biên dịch phàn nàn rằng 'Không thể tìm thấy loại hoặc không gian tên' myType '. "Phải có một cách để làm điều này.


Generics! = Mẫu. Tất cả các biến loại chung được giải quyết tại thời gian biên dịch và không phải trong thời gian chạy. Đây là một trong những tình huống mà loại 'động' 4.0 có thể hữu ích.

1
@ Sẽ - theo cách nào? Khi được sử dụng với generic, theo CTP hiện tại, về cơ bản bạn sẽ gọi các phiên bản <object> (trừ khi tôi thiếu một mẹo ...)
Marc Gravell

@MarcGravell bạn có thể sử dụng foo.Method((dynamic)myGenericClass)để liên kết phương thức thời gian chạy, hiệu quả là mẫu định vị dịch vụ cho tình trạng quá tải phương thức của loại.
Chris Marisic

@ChrisMarisic có, đối với một số người chung chung public void Method<T>(T obj)- một mẹo tôi đã sử dụng hơn một vài lần trong 6 năm qua kể từ nhận xét đó; p
Marc Gravell

@MarcGravell có cách nào để sửa đổi để phương thức khởi tạo nó không?
barlop

Câu trả lời:


220

Bạn không thể làm điều này mà không có sự phản ánh. Tuy nhiên, bạn có thể làm điều đó với sự phản ánh. Đây là một ví dụ hoàn chỉnh:

using System;
using System.Reflection;

public class Generic<T>
{
    public Generic()
    {
        Console.WriteLine("T={0}", typeof(T));
    }
}

class Test
{
    static void Main()
    {
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    }
}

Lưu ý: nếu lớp chung của bạn chấp nhận nhiều loại, bạn phải bao gồm dấu phẩy khi bạn bỏ qua tên loại, ví dụ:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);

1
OK, điều này là tốt, nhưng làm thế nào để gọi phương thức được tạo? Phản ánh nhiều hơn?
Robert C. Barth

7
Chà, nếu bạn có thể thoát khỏi việc làm cho loại chung của bạn thực hiện một giao diện không chung chung, bạn có thể chuyển sang giao diện đó. Ngoài ra, bạn có thể viết phương thức chung của riêng bạn, thực hiện tất cả công việc bạn muốn làm với chung và gọi phương thức đó bằng sự phản chiếu.
Jon Skeet

1
Vâng, tôi không thực hiện cách sử dụng được tạo nếu thông tin duy nhất bạn có về loại được trả về là trong biến loại typeArgument? Đối với tôi có vẻ như bạn sẽ phải sử dụng biến đó, nhưng bạn không biết nó là gì nên tôi không chắc liệu bạn có thể làm điều đó với sự phản chiếu hay không. Một câu hỏi khác nếu đối tượng là ví dụ của kiểu int nếu bạn chuyển nó dưới dạng biến đối tượng vào ví dụ nói fro một Danh sách <int> wil công việc này? biến được tạo sẽ được coi là một int?
theringostarrs

6
@ RobertC.Barth Bạn cũng có thể tạo đối tượng "đã tạo" trong loại ví dụ "động" thay vì "đối tượng". Bằng cách đó bạn có thể gọi các phương thức trên đó và việc đánh giá sẽ được hoãn lại cho đến khi thực thi.
McGarnagle

4
@balanza: Bạn sử dụng MakeGenericMethod.
Jon Skeet

14

Thật không may là không có. Các đối số chung phải được phân giải tại thời gian Biên dịch là 1) loại hợp lệ hoặc 2) tham số chung khác. Không có cách nào để tạo các thể hiện chung dựa trên các giá trị thời gian chạy mà không cần đến việc sử dụng sự phản chiếu.


2

Một số cách bổ sung để chạy với mã kéo. Giả sử bạn có một lớp tương tự

public class Encoder() {
public void Markdown(IEnumerable<FooContent> contents) { do magic }
public void Markdown(IEnumerable<BarContent> contents) { do magic2 }
}

Giả sử trong thời gian chạy bạn có FooContent

Nếu bạn có thể liên kết vào thời gian biên dịch, bạn sẽ muốn

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

Tuy nhiên bạn không thể làm điều này trong thời gian chạy. Để làm điều này trong thời gian chạy, bạn sẽ làm theo dòng:

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

Để tự động gọi Markdown(IEnumerable<FooContent> contents)

new Encoder().Markdown( (dynamic) dynamicList)

Lưu ý việc sử dụng dynamictrong cuộc gọi phương thức. Trong thời gian chạy dynamicListsẽ List<FooContent>(ngoài ra cũng vậy IEnumerable<FooContent>) vì ngay cả việc sử dụng động vẫn bắt nguồn từ một ngôn ngữ được gõ mạnh, chất kết dính thời gian chạy sẽ chọn Markdownphương thức thích hợp . Nếu không có loại khớp chính xác, nó sẽ tìm một phương thức tham số đối tượng và nếu không khớp với ngoại lệ liên kết thời gian chạy sẽ được đưa ra cảnh báo rằng không có phương thức nào khớp.

Điểm thu hút rõ ràng của phương pháp này là sự mất mát lớn về an toàn kiểu tại thời điểm biên dịch. Tuy nhiên, mã dọc theo các dòng này sẽ cho phép bạn hoạt động theo một nghĩa rất năng động rằng tại thời gian chạy vẫn được gõ đầy đủ như bạn mong đợi.


2

Yêu cầu của tôi hơi khác một chút, nhưng hy vọng sẽ giúp được ai đó. Tôi cần phải đọc kiểu từ một cấu hình và khởi tạo kiểu chung một cách linh hoạt.

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

Cuối cùng, đây là cách bạn gọi nó. Xác định loại với một backtick .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);

0

Nếu bạn biết loại nào sẽ được thông qua, bạn có thể làm điều này mà không cần phản ánh. Một tuyên bố chuyển đổi sẽ làm việc. Rõ ràng, điều này sẽ chỉ hoạt động trong một số trường hợp hạn chế, nhưng nó sẽ nhanh hơn nhiều so với phản xạ.

public class Type1 { }

public class Type2 { }

public class Generic<T> { }

public class Program
{
    public static void Main()
    {
        var typeName = nameof(Type1);

        switch (typeName)
        {
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        }
    }
}

điều này trở nên tồi tệ nhanh chóng khi bạn bắt đầu giao dịch với 100 lớp.
michael g

0

Trong đoạn trích này tôi muốn chỉ ra cách tạo và sử dụng danh sách được tạo động. Ví dụ: tôi đang thêm vào danh sách động ở đây.

void AddValue<T>(object targetList, T valueToAdd)
{
    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[] { valueToAdd } as object[]);
}

var listType = typeof(List<>).MakeGenericType(new[] { dynamicType }); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

Tương tự, bạn có thể gọi bất kỳ phương thức nào khác trong danh sách.

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.