มีแนวทางที่สมเหตุสมผลสำหรับพารามิเตอร์ประเภท "เริ่มต้น" ใน C # Generics หรือไม่


93

ในเทมเพลต C ++ เราสามารถระบุว่าพารามิเตอร์บางประเภทเป็นค่าเริ่มต้น ยกเว้นที่ระบุไว้อย่างชัดเจนจะใช้ประเภท T.

สามารถทำได้หรือประมาณใน C #?

ฉันกำลังมองหาสิ่งที่ต้องการ:

public class MyTemplate<T1, T2=string> {}

ดังนั้นอินสแตนซ์ของประเภทที่ไม่ได้ระบุอย่างชัดเจนT2:

MyTemplate<int> t = new MyTemplate<int>();

โดยพื้นฐานแล้วจะเป็น:

MyTemplate<int, string> t = new MyTemplate<int, string>();

ในที่สุดฉันกำลังดูกรณีที่มีเทมเพลตที่ค่อนข้างใช้กันอย่างแพร่หลาย แต่ฉันกำลังพิจารณาขยายด้วยพารามิเตอร์ประเภทเพิ่มเติม ฉันสามารถคลาสย่อยได้ฉันเดา แต่ฉันอยากรู้ว่ามีตัวเลือกอื่นในหลอดเลือดดำนี้หรือไม่

คำตอบ:


77

คลาสย่อยเป็นตัวเลือกที่ดีที่สุด

ฉันจะแบ่งคลาสย่อยหลักของคุณ:

class BaseGeneric<T,U>

กับคลาสเฉพาะ

class MyGeneric<T> : BaseGeneric<T, string>

สิ่งนี้ทำให้ง่ายต่อการเก็บตรรกะของคุณไว้ในที่เดียว (คลาสพื้นฐาน) แต่ยังให้ตัวเลือกการใช้งานทั้งสองแบบได้อย่างง่ายดาย ขึ้นอยู่กับชั้นเรียนอาจมีงานพิเศษเล็กน้อยที่จำเป็นเพื่อให้สิ่งนี้เกิดขึ้น


1
อา ... นั่นเข้าท่า ชื่อประเภทอนุญาตให้เหมือนกันหรือไม่หากพารามิเตอร์ type มีลายเซ็นเฉพาะ
el2iot2

2
@ee: ใช่ generics overloadableตามจำนวนพารามิเตอร์
Mehrdad Afshari

@ee: ใช่ แต่ฉันจะระมัดระวังในการทำเช่นนั้น การทำเช่นนั้น "ถูกกฎหมาย" ใน. NET แต่อาจทำให้เกิดความสับสนได้ ฉันอยากให้ชื่อประเภทที่ได้มาของสตริงนั้นคล้ายกับคลาสทั่วไปหลัก (ดังนั้นจึงชัดเจนว่ามันคืออะไร / หาง่าย) แต่ชื่อที่ทำให้ชัดเจนว่าเป็นสตริง
Reed Copsey

@Reed: มันทำให้สับสนจริงหรือ? สำหรับกรณีนี้ฉันคิดว่าการมีชื่อเดียวกันจะช่วยได้ มีตัวอย่างใน. NET ที่ทำสิ่งเดียวกัน: Func <> delegate เช่น
Mehrdad Afshari

4
ตัวอย่างเช่นเพรดิเคต <T> เป็นเพียง Func <T, bool> แต่ถูกเปลี่ยนชื่อเนื่องจากมีวัตถุประสงค์อื่น
Reed Copsey

19

ทางออกหนึ่งคือการแบ่งคลาสย่อย อีกวิธีหนึ่งที่ฉันจะใช้แทนคือวิธีการโรงงาน (รวมกับคำหลัก var)

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

ในตัวอย่างข้างต้นval2เป็นประเภทMyTemplate<int,string> และไม่ใช่ประเภทที่ได้มาจากมัน

ชนิดที่ไม่ได้เป็นประเภทเดียวกับclass MyStringTemplate<T>:MyTemplate<T,string> MyTemplate<T,string>ซึ่งอาจก่อให้เกิดปัญหาบางอย่างในบางสถานการณ์ ตัวอย่างเช่นคุณไม่สามารถโยนตัวอย่างของการMyTemplate<T,string>MyStringTemplate<T>


3
นี่เป็นแนวทางที่ใช้ได้มากที่สุด ทางออกที่ดีมาก
T-moty

12

คุณยังสามารถสร้างคลาส Overload ได้เช่นกัน

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}

โปรดอ่านคำตอบอื่น ๆ ก่อนที่คุณจะโพสต์คำตอบที่ล่าช้าเนื่องจากวิธีการแก้ปัญหาของคุณอาจจะเหมือนกับคำตอบอื่น ๆ
Cheng Chen

7
คำตอบที่ยอมรับคือการสร้างคลาสที่มีชื่อแตกต่างกันวิธีแก้ปัญหาของฉันคือการโอเวอร์โหลดคลาสเดียวกัน
Nerdroid

2
ไม่ทั้งคู่กำลังสร้างคลาสใหม่ ชื่อไม่สำคัญที่นี่ MyTemplate<T1>เป็นคลาสที่แตกต่างจากMyTemplate<T1, T2>ไม่AnotherTemplate<T1>เหมือนกัน
Cheng Chen

7
แม้ว่าประเภทของจริงจะแตกต่างกันซึ่งชัดเจนและถูกต้อง แต่การเข้ารหัสจะง่ายกว่าหากคุณใช้ชื่อเดียวกัน ไม่ชัดเจนสำหรับทุกคนว่าคลาส "เกิน" สามารถใช้แบบนั้นได้ @DannyChen ถูกต้อง - จากมุมมองทางเทคนิคผลลัพธ์เหมือนกัน แต่ answear นี้ใกล้เคียงกว่าที่จะบรรลุสิ่งที่ OP ขอ
Kuba

1
น่าเสียดายที่โซลูชันนี้ยังด้อยกว่าอาร์กิวเมนต์ทั่วไปที่เป็น "ค่าเริ่มต้น" จริงเนื่องจากMyTemplate<T1, string>ไม่สามารถกำหนดให้MyTemplate<T1>ซึ่งสามารถ
ปรารถนาได้

10

C # ไม่รองรับคุณสมบัติดังกล่าว

อย่างที่คุณบอกคุณสามารถคลาสย่อยได้ (หากไม่ได้ปิดผนึกและทำซ้ำการประกาศตัวสร้างทั้งหมด) แต่มันเป็นสิ่งที่แตกต่างกัน


3

น่าเสียดายที่ C # ไม่สนับสนุนสิ่งที่คุณพยายามทำ จะเป็นคุณลักษณะที่ยากในการนำไปใช้เนื่องจากประเภทเริ่มต้นสำหรับพารามิเตอร์จะต้องเป็นไปตามข้อ จำกัด ทั่วไปและมักจะสร้างความปวดหัวเมื่อ CLR พยายามตรวจสอบความปลอดภัยของประเภท


1
ไม่จริง. สามารถทำได้ด้วยแอตทริบิวต์ (เช่นพารามิเตอร์เริ่มต้นใน VB.NET) และให้คอมไพเลอร์แทนที่ในเวลาคอมไพล์ เหตุผลหลักคือเป้าหมายการออกแบบ C #
Mehrdad Afshari

คอมไพลเลอร์ต้องตรวจสอบให้แน่ใจว่าพารามิเตอร์ดีฟอลต์ตรงตามข้อ จำกัด ทั่วไป นอกจากนี้พารามิเตอร์ดีฟอลต์จะเป็นข้อ จำกัด ทั่วไปเนื่องจากสมมติฐานใด ๆ ที่เกิดขึ้นเกี่ยวกับพารามิเตอร์ type ในเมธอดต้องการให้พารามิเตอร์ประเภทที่ไม่ใช่ค่าดีฟอลต์สืบทอดจากพารามิเตอร์นั้น
Andrew Hare

@ แอนดรูว์พารามิเตอร์เริ่มต้นไม่จำเป็นต้องเป็นข้อ จำกัด ทั่วไป หากสิ่งนี้ทำงานเหมือนพารามิเตอร์เทมเพลตเริ่มต้นใน C ++ มากขึ้นการขยายตามคลาสของออโตนิกจะเป็นการดีอย่างยิ่ง: MyTemplate <int, float> x = null เนื่องจาก T2 ไม่มีข้อ จำกัด ทั่วไปดังนั้น float จึงใช้ได้แม้จะมีประเภทสตริงเริ่มต้น . ด้วยวิธีนี้พารามิเตอร์เทมเพลตเริ่มต้นโดยพื้นฐานแล้วเป็นเพียง "น้ำตาลสังเคราะห์" สำหรับการเขียน MyTemplate <int> เป็นชวเลขสำหรับ MyTemplate <int, string>
Tyler Laing

@ แอนดรูฉันยอมรับว่าคอมไพลเลอร์ C # ควรตรวจสอบว่าพารามิเตอร์เทมเพลตเริ่มต้นดังกล่าวตรงตามค่า Conststaints ทั่วไปที่มีอยู่ คอมไพเลอร์ C # ได้ตรวจสอบสิ่งที่คล้ายกันในการประกาศคลาสแล้วเช่นเพิ่มข้อ จำกัด ทั่วไปเช่น "where U: ISomeInterface" ไปยังคลาส BaseGeneric <T, U> ของ Reed จากนั้น MyGeneric <T> จะไม่สามารถรวบรวมได้โดยมีข้อผิดพลาด การตรวจสอบพารามิเตอร์เทมเพลตเริ่มต้นจะเหมือนกันมาก: คอมไพเลอร์ตรวจสอบตามการประกาศคลาสและข้อความแสดงข้อผิดพลาดอาจเหมือนกันหรือคล้ายกันมาก
Tyler Laing

"มันจะเป็นคุณสมบัติที่ยากในการนำไปใช้" นั่นเป็นเรื่องไร้สาระ มันจะเป็นเรื่องเล็กน้อยที่จะนำไปใช้ งานในการตรวจสอบความถูกต้องของประเภทกับข้อ จำกัด ของเทมเพลตไม่ได้ยากขึ้นเนื่องจากประเภทของตัวเลือกปรากฏในตำแหน่งอื่นในไฟล์ (เช่นในเทมเพลตแทนที่จะเป็นในการสร้างอินสแตนซ์เทมเพลต)
โคลน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.