Gọi một phương thức chung với tham số loại chỉ được biết đến trong thời gian chạy có thể được đơn giản hóa rất nhiều bằng cách sử dụng một dynamic
loại thay vì API phản chiếu.
Để sử dụng kỹ thuật này, loại phải được biết từ đối tượng thực tế (không chỉ là một thể hiện của Type
lớp). Mặt khác, bạn phải tạo một đối tượng thuộc loại đó hoặc sử dụng giải pháp API phản chiếu tiêu chuẩn . Bạn có thể tạo một đối tượng bằng cách sử dụng phương thức Activator.CreateInstance .
Nếu bạn muốn gọi một phương thức chung, thì trong cách sử dụng "bình thường" sẽ có kiểu được suy ra, thì nó chỉ đơn giản là chuyển đối tượng thuộc loại không xác định sang dynamic
. Đây là một ví dụ:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
Và đây là đầu ra của chương trình này:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
là một phương thức cá thể chung viết kiểu thực của đối số được truyền (bằng cách sử dụng GetType()
phương thức) và kiểu của tham số chung (bằng cách sử dụng typeof
toán tử).
Bằng cách chuyển đối số đối tượng thành dynamic
loại, chúng tôi đã hoãn cung cấp tham số loại cho đến khi thời gian chạy. Khi Process
phương thức được gọi với dynamic
đối số thì trình biên dịch không quan tâm đến kiểu của đối số này. Trình biên dịch tạo mã mà trong thời gian chạy kiểm tra các loại đối số được truyền thực sự (bằng cách sử dụng sự phản chiếu) và chọn phương thức tốt nhất để gọi. Ở đây chỉ có một phương thức chung này, vì vậy nó được gọi với một tham số loại thích hợp.
Trong ví dụ này, đầu ra giống như khi bạn viết:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
Phiên bản với loại động chắc chắn là ngắn hơn và dễ viết hơn. Bạn cũng không nên lo lắng về hiệu suất của việc gọi chức năng này nhiều lần. Cuộc gọi tiếp theo với các đối số cùng loại sẽ nhanh hơn nhờ cơ chế lưu vào bộ đệm trong DLR. Tất nhiên, bạn có thể viết mã mà bộ nhớ cache đã gọi các đại biểu, nhưng bằng cách sử dụng dynamic
loại bạn có được hành vi này miễn phí.
Nếu phương thức chung mà bạn muốn gọi không có đối số của kiểu tham số hóa (vì vậy tham số loại của nó không thể được suy ra) thì bạn có thể gói lời gọi của phương thức chung trong phương thức trợ giúp như trong ví dụ sau:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
Tăng độ an toàn
Điều thực sự tuyệt vời khi sử dụng dynamic
đối tượng thay thế cho việc sử dụng API phản chiếu là bạn chỉ mất thời gian biên dịch kiểm tra loại cụ thể này mà bạn không biết cho đến khi chạy. Các đối số khác và tên của phương thức được trình biên dịch phân tích tĩnh như bình thường. Nếu bạn xóa hoặc thêm nhiều đối số, thay đổi loại hoặc đổi tên phương thức thì bạn sẽ gặp lỗi thời gian biên dịch. Điều này sẽ không xảy ra nếu bạn cung cấp tên phương thức dưới dạng một chuỗi trong Type.GetMethod
và đối số là mảng đối tượng MethodInfo.Invoke
.
Dưới đây là một ví dụ đơn giản minh họa cách một số lỗi có thể bị bắt tại thời gian biên dịch (mã nhận xét) và lỗi khác khi chạy. Nó cũng cho thấy DLR cố gắng giải quyết phương thức nào sẽ gọi.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
Ở đây chúng ta lại thực hiện một số phương thức bằng cách chuyển đối số thành dynamic
kiểu. Chỉ xác minh loại đối số đầu tiên được hoãn vào thời gian chạy. Bạn sẽ gặp lỗi trình biên dịch nếu tên của phương thức bạn gọi không tồn tại hoặc nếu các đối số khác không hợp lệ (sai số lượng đối số hoặc loại sai).
Khi bạn truyền dynamic
đối số cho một phương thức thì cuộc gọi này gần đây bị ràng buộc . Phương pháp giải quyết quá tải xảy ra trong thời gian chạy và cố gắng chọn quá tải tốt nhất. Vì vậy, nếu bạn gọi ProcessItem
phương thức với một đối tượng BarItem
kiểu thì bạn thực sự sẽ gọi phương thức không chung chung, bởi vì nó phù hợp hơn với loại này. Tuy nhiên, bạn sẽ gặp lỗi thời gian chạy khi bạn chuyển một đối số của Alpha
loại vì không có phương thức nào có thể xử lý đối tượng này (một phương thức chung có ràng buộc where T : IItem
và Alpha
lớp không thực hiện giao diện này). Nhưng đó là toàn bộ vấn đề. Trình biên dịch không có thông tin rằng cuộc gọi này là hợp lệ. Bạn là một lập trình viên biết điều này và bạn nên chắc chắn rằng mã này chạy không có lỗi.
Kiểu trả về
Khi bạn gọi một phương pháp phi khoảng trống với một tham số của loại năng động, kiểu trả về của nó có thể sẽ là dynamic
quá . Vì vậy, nếu bạn thay đổi ví dụ trước thành mã này:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
thì loại đối tượng kết quả sẽ là dynamic
. Điều này là do trình biên dịch không luôn biết phương thức nào sẽ được gọi. Nếu bạn biết kiểu trả về của lệnh gọi hàm thì bạn nên ngầm chuyển nó thành kiểu được yêu cầu để phần còn lại của mã được nhập tĩnh:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Bạn sẽ gặp lỗi thời gian chạy nếu loại không khớp.
Thực tế, nếu bạn cố gắng lấy giá trị kết quả trong ví dụ trước thì bạn sẽ gặp lỗi thời gian chạy trong vòng lặp thứ hai. Điều này là do bạn đã cố lưu giá trị trả về của hàm void.