การเรียกใช้เมธอดทั่วไปที่มีพารามิเตอร์ type ที่รู้จักกันเฉพาะ ณ รันไทม์สามารถทำให้ง่ายขึ้นอย่างมากโดยใช้dynamic
ชนิดแทน API การสะท้อน
ในการใช้เทคนิคนี้จะต้องรู้ชนิดจากวัตถุจริง (ไม่ใช่เพียงตัวอย่างของType
คลาส) มิฉะนั้นคุณจะต้องสร้างวัตถุประเภทนั้นหรือใช้มาตรฐาน API สะท้อนการแก้ปัญหา คุณสามารถสร้างวัตถุโดยใช้วิธีการActivator.CreateInstance
หากคุณต้องการที่จะเรียกวิธีการทั่วไปว่าใน "ปกติ" dynamic
การใช้งานจะมีประเภทของสรุปแล้วมันก็มาถึงการหล่อวัตถุชนิดที่ไม่รู้จักที่จะ นี่คือตัวอย่าง:
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)"
}
}
}
และนี่คือผลลัพธ์ของโปรแกรมนี้:
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
เป็นวิธีอินสแตนซ์ทั่วไปที่เขียนชนิดที่แท้จริงของอาร์กิวเมนต์ที่ส่งผ่าน (โดยใช้GetType()
วิธีการ) และชนิดของพารามิเตอร์ทั่วไป (โดยใช้typeof
โอเปอเรเตอร์)
โดยการคัดเลือกอากิวเมนต์ object เพื่อdynamic
พิมพ์เราเลื่อนการจัดเตรียมพารามิเตอร์ type ไว้จนกระทั่งรันไทม์ เมื่อProcess
วิธีการถูกเรียกด้วยการdynamic
โต้แย้งแล้วคอมไพเลอร์ไม่สนใจเกี่ยวกับประเภทของการโต้แย้งนี้ คอมไพเลอร์สร้างรหัสที่รันไทม์ตรวจสอบชนิดของอาร์กิวเมนต์ที่ส่งผ่านจริง (โดยใช้การสะท้อนกลับ) และเลือกวิธีที่ดีที่สุดในการโทร ที่นี่มีวิธีการทั่วไปเพียงวิธีเดียวเท่านั้นดังนั้นจึงเรียกใช้ด้วยพารามิเตอร์ชนิดที่เหมาะสม
ในตัวอย่างนี้ผลลัพธ์จะเหมือนกับคุณเขียนว่า:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
รุ่นที่มีประเภทไดนามิกจะสั้นกว่าและเขียนง่ายกว่า คุณไม่ควรกังวลเกี่ยวกับประสิทธิภาพของการเรียกใช้ฟังก์ชันนี้หลายครั้ง การเรียกครั้งต่อไปที่มีอาร์กิวเมนต์ประเภทเดียวกันควรเร็วขึ้นเนื่องจากกลไกการแคชใน DLR แน่นอนคุณสามารถเขียนรหัสที่ผู้ใช้แคชเรียก แต่โดยใช้dynamic
ประเภทที่คุณได้รับพฤติกรรมนี้ได้ฟรี
หากวิธีการทั่วไปที่คุณต้องการโทรไม่มีอาร์กิวเมนต์ประเภท parametrized (ดังนั้นพารามิเตอร์ประเภทของมันไม่สามารถอนุมานได้) จากนั้นคุณสามารถตัดคำอุทธรณ์ของวิธีการทั่วไปในวิธีผู้ช่วยเช่นในตัวอย่างต่อไปนี้:
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) + ">");
}
}
ความปลอดภัยประเภทที่เพิ่มขึ้น
สิ่งที่ยอดเยี่ยมมากเกี่ยวกับการใช้dynamic
อ็อบเจกต์แทนการใช้สะท้อน API คือคุณเสียเวลาตรวจสอบคอมไพล์ของประเภทเฉพาะที่คุณไม่รู้จนกระทั่งรันไทม์ ข้อโต้แย้งอื่น ๆ และชื่อของวิธีการวิเคราะห์แบบคงที่โดยคอมไพเลอร์ตามปกติ หากคุณลบหรือเพิ่มข้อโต้แย้งเพิ่มเติมให้เปลี่ยนประเภทหรือเปลี่ยนชื่อวิธีการจากนั้นคุณจะได้รับข้อผิดพลาดเวลารวบรวม นี้จะไม่เกิดขึ้นถ้าคุณให้ชื่อวิธีการที่เป็นสตริงในและข้อโต้แย้งเป็นวัตถุในอาร์เรย์Type.GetMethod
MethodInfo.Invoke
ด้านล่างนี้เป็นตัวอย่างง่ายๆที่แสดงให้เห็นว่าสามารถจับข้อผิดพลาดบางอย่างในเวลารวบรวม (โค้ดที่ใส่ความคิดเห็น) และอื่น ๆ ในขณะทำงาน นอกจากนี้ยังแสดงให้เห็นว่า DLR พยายามแก้ไขวิธีการโทรแบบใด
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);
}
}
ที่นี่เราดำเนินการวิธีการอีกครั้งโดยการส่งอาร์กิวเมนต์ไปยังdynamic
ประเภท การตรวจสอบประเภทอาร์กิวเมนต์แรกเท่านั้นที่จะเลื่อนออกไปเป็นรันไทม์ คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์หากชื่อของวิธีการที่คุณโทรไม่มีอยู่หรือหากข้อโต้แย้งอื่นไม่ถูกต้อง (จำนวนอาร์กิวเมนต์ที่ไม่ถูกต้องหรือประเภทที่ไม่ถูกต้อง)
เมื่อคุณผ่านการdynamic
โต้แย้งกับวิธีการแล้วสายนี้ถูกผูกไว้เมื่อเร็ว ๆ นี้ วิธีแก้ไขการโอเวอร์โหลดเกิดขึ้นที่รันไทม์และพยายามเลือกโอเวอร์โหลดที่ดีที่สุด ดังนั้นถ้าคุณเรียกใช้ProcessItem
เมธอดด้วยอ็อบเจกต์BarItem
ประเภทคุณจะต้องเรียกเมธอดที่ไม่ใช่แบบทั่วไปเพราะมันเป็นการจับคู่ที่ดีกว่าสำหรับประเภทนี้ อย่างไรก็ตามคุณจะได้รับข้อผิดพลาดรันไทม์เมื่อคุณผ่านการโต้แย้งของAlpha
ประเภทเพราะไม่มีวิธีที่สามารถจัดการวัตถุนี้ (วิธีการทั่วไปมีข้อ จำกัดwhere T : IItem
และAlpha
ชั้นไม่ได้ใช้อินเตอร์เฟซนี้) แต่นั่นคือประเด็นทั้งหมด คอมไพเลอร์ไม่มีข้อมูลว่าการโทรนี้ถูกต้อง คุณในฐานะโปรแกรมเมอร์รู้เรื่องนี้และคุณควรตรวจสอบให้แน่ใจว่าโค้ดนี้ทำงานโดยไม่มีข้อผิดพลาด
ประเภทผลตอบแทน gotcha
เมื่อคุณเรียกใช้เมธอดที่ไม่เป็นโมฆะโดยใช้พารามิเตอร์ชนิดไดนามิกประเภทการส่งคืนอาจเป็นdynamic
เช่นนั้น ดังนั้นหากคุณต้องการเปลี่ยนตัวอย่างก่อนหน้าเป็นรหัสนี้:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
dynamic
แล้วชนิดของวัตถุผลที่ได้จะเป็น นี่เป็นเพราะคอมไพเลอร์ไม่เคยรู้ว่าจะเรียกวิธีใด หากคุณรู้ว่าชนิดการส่งคืนของการเรียกใช้ฟังก์ชันคุณควรแปลงเป็นประเภทที่ต้องการโดยปริยายดังนั้นรหัสที่เหลือจะถูกพิมพ์แบบคงที่:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
คุณจะได้รับข้อผิดพลาดรันไทม์หากประเภทไม่ตรงกัน
ที่จริงแล้วถ้าคุณพยายามที่จะรับค่าผลลัพธ์ในตัวอย่างก่อนหน้านี้คุณจะได้รับข้อผิดพลาดรันไทม์ในการวนซ้ำที่สอง นี่เป็นเพราะคุณพยายามบันทึกค่าส่งคืนของฟังก์ชัน void