C # 4.0 แนะนำประเภทใหม่ที่เรียกว่า 'ไดนามิก' ทุกอย่างฟังดูดี แต่โปรแกรมเมอร์จะใช้อะไร?
มีสถานการณ์ที่สามารถช่วยชีวิตวันได้หรือไม่?
C # 4.0 แนะนำประเภทใหม่ที่เรียกว่า 'ไดนามิก' ทุกอย่างฟังดูดี แต่โปรแกรมเมอร์จะใช้อะไร?
มีสถานการณ์ที่สามารถช่วยชีวิตวันได้หรือไม่?
คำตอบ:
คีย์เวิร์ดไดนามิกนั้นใหม่สำหรับ C # 4.0 และใช้เพื่อบอกคอมไพเลอร์ว่าชนิดของตัวแปรสามารถเปลี่ยนแปลงได้หรือไม่รู้จักจนกระทั่งรันไทม์ คิดว่ามันสามารถโต้ตอบกับวัตถุได้โดยไม่ต้องร่าย
dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
แจ้งให้ทราบว่าเราไม่จำเป็นต้องส่งหรือประกาศการเป็นลูกค้าประเภท เนื่องจากเราประกาศว่าเป็นแบบไดนามิกรันไทม์จะเข้าควบคุมและค้นหาและตั้งค่าคุณสมบัติ FirstName สำหรับเรา ตอนนี้แน่นอนเมื่อคุณใช้ตัวแปรแบบไดนามิกคุณกำลังเลิกการตรวจสอบประเภทคอมไพเลอร์ ซึ่งหมายความว่าการเรียก cust.MissingMethod () จะรวบรวมและไม่ล้มเหลวจนกว่ารันไทม์ ผลลัพธ์ของการดำเนินการนี้คือ RuntimeBinderException เนื่องจาก MissingMethod ไม่ได้ถูกกำหนดไว้ในคลาสลูกค้า
ตัวอย่างด้านบนแสดงวิธีการทำงานของไดนามิกเมื่อเรียกใช้เมธอดและคุณสมบัติ คุณลักษณะที่มีประสิทธิภาพ (และอาจเป็นอันตราย) อีกประการหนึ่งคือการสามารถนำตัวแปรมาใช้ใหม่สำหรับข้อมูลประเภทต่างๆ ฉันแน่ใจว่าโปรแกรมเมอร์ Python, Ruby และ Perl ออกไปสามารถคิดได้หลายล้านวิธีในการใช้ประโยชน์จากสิ่งนี้ แต่ฉันใช้ C # มานานจนรู้สึกว่า "ผิด" กับฉัน
dynamic foo = 123;
foo = "bar";
ตกลงดังนั้นคุณมักจะไม่เขียนโค้ดเหมือนด้านบนบ่อยมาก อย่างไรก็ตามอาจมีบางครั้งที่การใช้ซ้ำตัวแปรสามารถใช้งานได้สะดวกหรือทำความสะอาดโค้ดที่สกปรก กรณีง่าย ๆ ที่ฉันพบเจอบ่อยครั้งคือต้องทำการคำนวณระหว่างทศนิยมและทศนิยมสองเท่า
decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
บรรทัดที่สองไม่ได้คอมไพล์เพราะ 2.5 ถูกพิมพ์เป็น double และ line 3 ไม่ได้คอมไพล์เพราะ Math.Sqrt ต้องการ double เห็นได้ชัดว่าสิ่งที่คุณต้องทำคือการส่งและ / หรือเปลี่ยนประเภทตัวแปรของคุณ แต่อาจมีสถานการณ์ที่การใช้แบบไดนามิกเหมาะสม
dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");
อ่านคุณสมบัติเพิ่มเติม: http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
in # ในการแก้ปัญหาที่สามารถแก้ไขได้ (อาจจะดีกว่า) โดยคุณสมบัติมาตรฐาน c # และการพิมพ์แบบสแตติกหรืออย่างมากกับการอนุมานประเภท ( var
) dynamic
ควรเพียง แต่นำมาใช้เมื่อมันมาถึงประเด็น interoperabilty กับ DLR หากคุณเขียนโค้ดในภาษาที่พิมพ์แบบคงที่เช่น c # คือให้ทำและไม่เลียนแบบภาษาแบบไดนามิก นั่นเป็นเพียงน่าเกลียด
dynamic
ตัวแปรจำนวนมากในรหัสของคุณซึ่งคุณไม่ต้องการตัวแปรเหล่านั้น (เช่นในตัวอย่างของคุณกับ squareroot) คุณจะต้องตรวจสอบข้อผิดพลาดในการคอมไพล์เวลาคอมไพล์ แทนตอนนี้คุณได้รับข้อผิดพลาด runtime ที่เป็นไปได้
dynamic
คำหลักที่ถูกเพิ่มเข้ามาพร้อมกับคุณสมบัติใหม่อื่น ๆ ของ C # 4.0 ที่จะทำให้มันง่ายที่จะพูดคุยกับรหัสที่อาศัยอยู่ในหรือมาจากเวลาการทำงานอื่น ๆ ที่มี APIs ที่แตกต่างกัน
ลองยกตัวอย่าง
หากคุณมีวัตถุ COM เช่น Word.Application
วัตถุและต้องการเปิดเอกสารวิธีการทำที่มาพร้อมกับพารามิเตอร์ไม่น้อยกว่า 15 ตัวซึ่งส่วนใหญ่เป็นตัวเลือก
หากต้องการเรียกวิธีนี้คุณต้องมีสิ่งนี้ (ฉันกำลังทำให้ง่ายขึ้นนี่ไม่ใช่รหัสจริง):
object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
หมายเหตุข้อโต้แย้งเหล่านั้นทั้งหมดหรือไม่ คุณต้องผ่านสิ่งเหล่านี้ตั้งแต่ C # ก่อนเวอร์ชัน 4.0 ไม่มีความคิดเห็นเกี่ยวกับอาร์กิวเมนต์ที่เป็นทางเลือก ใน C # 4.0 นั้น COM APIs สามารถทำงานได้ง่ายขึ้นโดยแนะนำ:
ref
ทางเลือกสำหรับ COM APIsไวยากรณ์ใหม่สำหรับการโทรด้านบนจะเป็น:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
มาดูกันว่ามันดูง่ายขึ้นมากแค่ไหนมันจะอ่านได้มากขึ้น?
เรามาแยกกัน:
named argument, can skip the rest
|
v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
^ ^
| |
notice no ref keyword, can pass
actual parameter values instead
ความมหัศจรรย์คือคอมไพเลอร์ C # จะฉีดโค้ดที่จำเป็นและทำงานกับคลาสใหม่ในรันไทม์เพื่อทำสิ่งเดียวกันกับที่คุณทำมาก่อน แต่ไวยากรณ์ถูกซ่อนไว้จากคุณตอนนี้คุณสามารถมุ่งเน้นไปที่อะไรและไม่มากในวิธีวิธีการAnders Hejlsberg ชอบที่จะบอกว่าคุณต้องเรียกใช้ "คาถา" ที่แตกต่างกันซึ่งเป็นการเรียงความของปุนเกี่ยวกับความมหัศจรรย์ของสิ่งทั้งปวงโดยทั่วไปคุณจะต้องโบกมือของคุณและพูดคำวิเศษในลำดับที่ถูกต้อง เพื่อให้ได้คาถาบางประเภท วิธี API แบบเก่าในการพูดคุยกับวัตถุ COM นั้นมีมากมายคุณต้องข้ามหลาย ๆ วงเพื่อที่จะเกลี้ยกล่อมคอมไพเลอร์เพื่อรวบรวมรหัสให้คุณ
สิ่งต่าง ๆ ใน C # ก่อนเวอร์ชัน 4.0 ยิ่งถ้าคุณพยายามพูดคุยกับวัตถุ COM ที่คุณไม่มีอินเทอร์เฟซหรือคลาสสำหรับสิ่งที่คุณมีคือการIDispatch
อ้างอิง
หากคุณไม่รู้ว่ามันคือIDispatch
อะไรโดยทั่วไปจะสะท้อนวัตถุ COM ด้วยIDispatch
อินเทอร์เฟซคุณสามารถถามวัตถุ "หมายเลข id สำหรับวิธีการที่รู้จักกันในชื่อ" คืออะไรและสร้างอาร์เรย์บางประเภทที่มีค่าอาร์กิวเมนต์และในที่สุดก็เรียกInvoke
วิธีการบนIDispatch
อินเตอร์เฟซเพื่อเรียกวิธีการผ่านทั้งหมด ข้อมูลที่คุณจัดการเพื่อขโมยด้วยกัน
วิธีการบันทึกข้างต้นอาจมีลักษณะเช่นนี้ (นี่ไม่ใช่รหัสที่ถูกต้อง):
string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);
ทั้งหมดนี้เพียงแค่เปิดเอกสาร
VB มีอาร์กิวเมนต์ที่เป็นตัวเลือกและการสนับสนุนสำหรับส่วนใหญ่ของกล่องนี้มานานแล้วดังนั้นรหัส C # นี้:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
โดยทั่วไปแล้วเพียงแค่ C # ถึง VB ในแง่ของการแสดงออก แต่ทำในสิ่งที่ถูกต้องด้วยการทำให้มันขยายได้และไม่ใช่แค่สำหรับ COM ของหลักสูตรนี้ยังมีอยู่สำหรับ VB.NET หรือภาษาอื่น ๆ ที่สร้างขึ้นบนรันไทม์. NET
คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับIDispatch
อินเทอร์เฟซบนWikipedia: IDispatchถ้าคุณต้องการอ่านเพิ่มเติมเกี่ยวกับมัน มันเต็มไปด้วยเลือด
อย่างไรก็ตามถ้าคุณต้องการคุยกับวัตถุ Python ล่ะ? มี API ที่แตกต่างจากที่ใช้กับวัตถุ COM และเนื่องจากวัตถุ Python นั้นเป็นแบบไดนามิกในธรรมชาติเช่นกันคุณต้องหันกลับไปใช้เวทมนตร์เพื่อค้นหาวิธีการที่เหมาะสมในการเรียกพารามิเตอร์ของพวกมันเป็นต้น แต่ไม่ใช่. NET การไตร่ตรองสิ่งที่เขียนสำหรับ Python ค่อนข้างคล้ายกับรหัส IDispatch ด้านบนแตกต่างกันโดยสิ้นเชิง
และสำหรับทับทิม? API อื่นยังคง
JavaScript? ข้อตกลงเดียวกัน API ที่แตกต่างกันเช่นกัน
คำหลักแบบไดนามิกประกอบด้วยสองสิ่ง:
dynamic
dynamic
คำหลักต้องการและแมปการโทรไปในทางที่ถูกต้องในการทำสิ่งต่าง ๆ API ยังมีการจัดทำเป็นเอกสารดังนั้นหากคุณมีวัตถุที่มาจากรันไทม์ที่ไม่ครอบคลุมคุณสามารถเพิ่มได้อย่างไรก็ตามdynamic
คำหลักนั้นไม่ได้หมายถึงการแทนที่รหัสใด ๆ . NET ที่มีอยู่เท่านั้น แน่นอนว่าคุณสามารถทำได้ แต่มันไม่ได้ถูกเพิ่มเข้ามาด้วยเหตุผลนั้นและผู้เขียนภาษา C # ที่มี Anders Hejlsberg อยู่ข้างหน้านั้นเป็นคนที่ยืนกรานว่าพวกเขายังคงมองว่า C # เป็นภาษาที่พิมพ์ออกมารุนแรงและจะไม่เสียสละ หลักการนั้น
ซึ่งหมายความว่าแม้ว่าคุณจะสามารถเขียนโค้ดแบบนี้ได้:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
และมันได้รวบรวมมันไม่ได้หมายความว่าเป็นประเภทของระบบมายากลช่วยให้คิดออกสิ่งที่คุณหมายถึงที่รันไทม์ของระบบ
จุดประสงค์ทั้งหมดคือเพื่อให้ง่ายต่อการพูดคุยกับวัตถุประเภทอื่น
มีเนื้อหามากมายบนอินเทอร์เน็ตเกี่ยวกับคำหลักผู้เสนอฝ่ายตรงข้ามการสนทนาการยกย่องสรรเสริญ ฯลฯ
ฉันขอแนะนำให้คุณเริ่มต้นด้วยลิงก์ต่อไปนี้และจากนั้น google เพื่อเพิ่มเติม:
dynamic
ถูกเพิ่มเข้ามาเพื่อสนับสนุนระบบนิเวศอื่น ๆ สำหรับวิธีการเรียกใช้เมธอดคล้ายภาพสะท้อนรวมถึงการจัดทำกล่องดำเรียงลำดับโครงสร้างข้อมูลด้วยวิธีการบันทึกเอกสารเพื่อให้บรรลุเป้าหมายนี้
ฉันประหลาดใจที่ไม่มีใครพูดถึงการส่งหลายครั้ง วิธีปกติในการแก้ไขปัญหานี้คือผ่านรูปแบบของผู้เข้าชมและเป็นไปไม่ได้เสมอดังนั้นคุณจึงจบด้วยการis
ตรวจสอบแบบซ้อน
ดังนั้นนี่คือตัวอย่างชีวิตจริงของแอปพลิเคชันของฉันเอง แทนที่จะทำ:
public static MapDtoBase CreateDto(ChartItem item)
{
if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
//other subtypes follow
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
คุณทำ:
public static MapDtoBase CreateDto(ChartItem item)
{
return CreateDtoImpl(item as dynamic);
}
private static MapDtoBase CreateDtoImpl(ChartItem item)
{
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
private static MapDtoBase CreateDtoImpl(MapPoint item)
{
return new MapPointDto(item);
}
private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
return new ElevationDto(item);
}
โปรดทราบว่าในกรณีแรกElevationPoint
คือ subclass ของMapPoint
และถ้ามันไม่ได้ถูกวางไว้ก่อนที่ MapPoint
มันจะไม่สามารถเข้าถึงได้ นี่ไม่ใช่กรณีที่มีไดนามิกเนื่องจากวิธีการจับคู่ที่ใกล้เคียงที่สุดจะถูกเรียก
อย่างที่คุณอาจเดาได้จากโค้ดคุณลักษณะนั้นมีประโยชน์ในขณะที่ฉันกำลังทำการแปลจากออบเจ็กต์ ChartItem ไปเป็นเวอร์ชั่นที่ต่อเนื่องกันได้ ฉันไม่ต้องการทำให้รหัสของฉันสกปรกกับผู้เยี่ยมชมและฉันก็ไม่ต้องการทำให้ChartItem
วัตถุของฉันสกปรกด้วยคุณลักษณะเฉพาะที่ทำให้เป็นอนุกรม
is
ของวงจรที่เพิ่มขึ้น
magic
; ไม่มีสิ่งใดที่เป็นเวทย์มนตร์
มันทำให้ง่ายขึ้นสำหรับภาษาที่พิมพ์แบบคงที่ (CLR) เพื่อทำงานร่วมกับภาษาแบบไดนามิก (หลาม, ทับทิม ... ) ที่ทำงานบน DLR (รันไทม์ภาษาแบบไดนามิก), ดูMSDN :
ตัวอย่างเช่นคุณอาจใช้รหัสต่อไปนี้เพื่อเพิ่มตัวนับใน XML ใน C #
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
โดยใช้ DLR คุณสามารถใช้รหัสต่อไปนี้แทนการดำเนินการเดียวกัน
scriptobj.Count += 1;
MSDN แสดงข้อดีเหล่านี้:
- ลดความยุ่งยากในการย้ายภาษาไดนามิกไปยัง. NET Framework
- เปิดใช้งานคุณสมบัติแบบไดนามิกในภาษาที่พิมพ์แบบคงที่
- ให้ประโยชน์ในอนาคตของ DLR และ. NET Framework
- เปิดใช้งานการแชร์ไลบรารีและวัตถุ
- จัดส่ง Fast Dynamic และการเรียกใช้
ดูMSDNสำหรับรายละเอียดเพิ่มเติม
ตัวอย่างการใช้งาน:
คุณใช้หลายคลาสที่มีคุณสมบัติชุมชน 'CreationDate':
public class Contact
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Company
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Opportunity
{
// some properties
public DateTime CreationDate { get; set; }
}
หากคุณเขียนวิธีการชุมชนที่ดึงค่าของคุณสมบัติ 'CreationDate' คุณจะต้องใช้การสะท้อน:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
ด้วยแนวคิด 'ไดนามิก' รหัสของคุณจะดูสง่างามกว่ามาก:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
COM interop โดยเฉพาะอย่างยิ่ง IUnknown มันถูกออกแบบมาเป็นพิเศษสำหรับมัน
ส่วนใหญ่มันจะถูกใช้โดยผู้ที่ตกเป็นเหยื่อ RAD และ Python เพื่อทำลายคุณภาพของรหัส, IntelliSenseและการตรวจจับข้อผิดพลาดในการรวบรวมเวลา
มันประเมินที่รันไทม์ดังนั้นคุณสามารถสลับชนิดอย่างที่คุณสามารถทำได้ใน JavaScript เป็นสิ่งที่คุณต้องการ นี่คือ Legit:
dynamic i = 12;
i = "text";
และเพื่อให้คุณสามารถเปลี่ยนประเภทตามที่คุณต้องการ ใช้เป็นทางเลือกสุดท้าย มันมีประโยชน์ แต่ฉันได้ยินมาหลายเรื่องในแง่ของการสร้าง IL และมันอาจมาในราคาแสดง
กรณีการใช้งานที่ดีที่สุดของตัวแปรประเภท 'ไดนามิก' สำหรับฉันคือเมื่อเร็ว ๆ นี้ฉันกำลังเขียนชั้นการเข้าถึงข้อมูลใน ADO.NET ( โดยใช้ SQLDataReader ) และรหัสถูกเรียกใช้ขั้นตอนการเก็บแบบดั้งเดิมที่เขียนไว้แล้ว มีวิธีการจัดเก็บแบบดั้งเดิมหลายร้อยรายการที่มีตรรกะทางธุรกิจจำนวนมาก ชั้นการเข้าถึงข้อมูลของฉันจำเป็นต้องส่งคืนข้อมูลที่มีโครงสร้างบางประเภทไปยังชั้นตรรกะทางธุรกิจซึ่งขึ้นกับ C # เพื่อทำการปรับแต่งบางอย่าง ( แม้ว่าจะแทบจะไม่มีเลยก็ตาม ) ทุกโพรซีเดอร์ที่เก็บไว้จะส่งคืนชุดข้อมูลที่แตกต่างกัน ( คอลัมน์ตาราง ) ดังนั้นแทนที่จะสร้างคลาสหรือโครงสร้างจำนวนมากเพื่อเก็บข้อมูลที่ส่งคืนและส่งไปยัง BLL ฉันจึงเขียนโค้ดด้านล่างซึ่งดูสวยและเรียบร้อย
public static dynamic GetSomeData(ParameterDTO dto)
{
dynamic result = null;
string SPName = "a_legacy_stored_procedure";
using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString))
{
SqlCommand command = new SqlCommand(SPName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
dynamic row = new ExpandoObject();
row.EmpName = reader["EmpFullName"].ToString();
row.DeptName = reader["DeptName"].ToString();
row.AnotherColumn = reader["AnotherColumn"].ToString();
result = row;
}
}
}
return result;
}
dynamic np = Py.Import("numpy")
dynamic
เมื่อใช้ตัวดำเนินการตัวเลขกับพวกเขา สิ่งนี้ให้ความปลอดภัยประเภทและหลีกเลี่ยงข้อ จำกัด ของข้อมูลทั่วไป นี่คือสาระสำคัญ * การพิมพ์เป็ด:T y = x * (dynamic)x
ที่ไหน typeof(x) is T
กรณีการใช้งานอื่นสำหรับการdynamic
พิมพ์เป็นวิธีการเสมือนที่ประสบปัญหาความแปรปรวนร่วมหรือความแปรปรวน ตัวอย่างหนึ่งคือClone
วิธีการที่น่าอับอายที่ส่งคืนวัตถุประเภทเดียวกันกับวัตถุที่ถูกเรียกใช้ ปัญหานี้ไม่ได้รับการแก้ไขอย่างสมบูรณ์ที่มีผลตอบแทนแบบไดนามิกเพราะมันทะลุตรวจสอบประเภทคงที่ object
แต่อย่างน้อยคุณไม่จำเป็นต้องใช้มันได้ปลดเปลื้องน่าเกลียดตลอดเวลาเป็นต่อเมื่อใช้ธรรมดา ไม่อย่างนั้นจะพูดได้ปลดเปลื้องกลายเป็นนัย
public class A
{
// attributes and constructor here
public virtual dynamic Clone()
{
var clone = new A();
// Do more cloning stuff here
return clone;
}
}
public class B : A
{
// more attributes and constructor here
public override dynamic Clone()
{
var clone = new B();
// Do more cloning stuff here
return clone;
}
}
public class Program
{
public static void Main()
{
A a = new A().Clone(); // No cast needed here
B b = new B().Clone(); // and here
// do more stuff with a and b
}
}