C # รองรับความแปรปรวนร่วมประเภทการส่งคืนหรือไม่?


88

ฉันกำลังทำงานกับ. NET framework และฉันต้องการสร้างหน้าที่กำหนดเองที่เว็บไซต์ของฉันใช้ทั้งหมด ปัญหาเกิดขึ้นเมื่อฉันพยายามเข้าถึงเพจจากตัวควบคุม ฉันต้องการให้สามารถส่งคืนเพจประเภทเฉพาะของฉันแทนเพจเริ่มต้นได้ มีวิธีใดบ้างที่จะทำเช่นนี้?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}

เป็นไปได้ที่จะซ้ำกันของประเภทc # covariant return โดยใช้ generics
nawfal

คำตอบ:


175

อัปเดต: คำตอบนี้เขียนขึ้นในปี 2554 หลังจากสองทศวรรษที่มีผู้เสนอความแปรปรวนร่วมประเภทผลตอบแทนสำหรับ C # ดูเหมือนว่าในที่สุดก็จะถูกนำไปใช้ ฉันค่อนข้างแปลกใจ ดูด้านล่างของhttps://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/การประกาศ; ฉันแน่ใจว่าจะมีรายละเอียดตามมา


ดูเหมือนว่าสิ่งที่คุณต้องการคือความแปรปรวนร่วมประเภทผลตอบแทน C # ไม่สนับสนุนความแปรปรวนร่วมประเภทการส่งคืน

ความแปรปรวนร่วมประเภทการส่งคืนคือที่ที่คุณแทนที่เมธอดคลาสพื้นฐานที่ส่งคืนชนิดที่เจาะจงน้อยกว่าโดยที่ส่งกลับประเภทที่เฉพาะเจาะจงมากขึ้น:

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

สิ่งนี้ปลอดภัยเนื่องจากผู้บริโภคเนื้อหาผ่านสิ่งที่ส่งมาด้วยคาดหวังว่าสัตว์และพิพิธภัณฑ์สัตว์น้ำสัญญาว่าจะไม่เพียงปฏิบัติตามข้อกำหนดนั้น แต่ยิ่งไปกว่านั้นจะให้คำมั่นสัญญาที่เข้มงวดยิ่งขึ้นนั่นคือสัตว์นั้นเป็นปลาเสมอ

C # ไม่รองรับความแปรปรวนร่วมประเภทนี้และไม่น่าจะได้รับการสนับสนุน CLR ไม่รองรับ (ได้รับการสนับสนุนโดย C ++ และโดยการใช้งาน C ++ / CLI บน CLR ทำได้โดยการสร้างเมธอดผู้ช่วยเวทย์มนตร์ของประเภทที่ฉันแนะนำด้านล่าง)

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

วิธีที่คุณสามารถแก้ไขข้อ จำกัด นี้คือทำสิ่งต่างๆเช่น:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

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


3
สมบูรณ์แบบ! ฉันลืมซ่อน!
Kyle Sletten

3
คำตอบที่ดีเช่นเคย เราพลาดคำตอบเหล่านั้นไปชั่วขณะ
Dhananjay

4
มีเหตุผลในการไม่สนับสนุนค่าความแปรปรวนร่วมผลตอบแทนและพารา - คอนดักเตอร์ที่อ่านได้ทุกที่หรือไม่
porges

26
@EricLippert ในฐานะ C # groupie ฉันมักจะสงสัยเกี่ยวกับ "หลังเวที" ดังนั้นฉันจึงต้องถาม: ความแปรปรวนร่วมประเภทผลตอบแทนที่เคยพิจารณาสำหรับ C # และถือว่ามี ROI ที่ต่ำเกินไปหรือไม่? ฉันเข้าใจข้อ จำกัด ของเวลาและจุดบกพร่อง แต่จากคำสั่ง "ไม่น่าจะรองรับ" ของคุณดูเหมือนว่าทีมออกแบบจะไม่มีสิ่งนี้ในภาษาจริงๆ ในอีกด้านหนึ่งของรั้ว Java ได้เปิดตัวคุณลักษณะภาษานี้ใน Java 5 ดังนั้นฉันจึงสงสัยว่าความแตกต่างคืออะไรในหลักการที่ใหญ่กว่าและครอบคลุมที่ควบคุมกระบวนการออกแบบภาษา
Cristian Diaconescu

7
@Cyral: ถูกต้อง; อยู่ในรายชื่อในที่เก็บข้อมูล "ดอกเบี้ยปานกลาง" ซึ่งเป็นที่ที่มีมานานแล้ว! มันอาจเกิดขึ้นได้ แต่ฉันค่อนข้างไม่เชื่อ
Eric Lippert

8

ด้วยอินเทอร์เฟซฉันสามารถใช้งานได้โดยใช้อินเทอร์เฟซอย่างชัดเจน:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}

2

การวางสิ่งนี้ในวัตถุ MyControl จะได้ผล:

 public new MyPage Page {get return (MyPage)Page; set;}'

คุณไม่สามารถแทนที่คุณสมบัติได้เนื่องจากส่งคืนเป็นประเภทอื่น ... แต่คุณสามารถกำหนดใหม่ได้

คุณไม่จำเป็นต้องมีความแปรปรวนร่วมในตัวอย่างนี้เนื่องจากค่อนข้างง่าย สิ่งที่คุณกำลังทำคือการสืบทอดวัตถุฐานจากPage MyPageใด ๆControlที่คุณต้องการส่งคืนMyPageแทนที่จะPageต้องกำหนดPageคุณสมบัติของไฟล์Control


1

ใช่มันสนับสนุนความแปรปรวนร่วม แต่ขึ้นอยู่กับสิ่งที่คุณพยายามจะบรรลุ

ฉันมักจะใช้ยาชื่อสามัญกับสิ่งต่างๆมากมายซึ่งหมายความว่าเมื่อคุณทำสิ่งต่างๆเช่น:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

และไม่ "เสีย" ประเภทของคุณ


1

นี่คือฟีเจอร์สำหรับC # 9.0 (.Net 5) ที่กำลังจะมาถึงซึ่งคุณสามารถดาวน์โหลดเวอร์ชันตัวอย่างได้ทันที

รหัสต่อไปนี้ในขณะนี้สร้างเสร็จเรียบร้อยแล้ว (โดยไม่ต้องให้: error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()')

class Food { }
class Meat : Food { }

abstract class Animal {
    public abstract Food GetFood();
}

class Tiger : Animal {
    public override Meat GetFood() => default;
}

class Program {
    static void Main() => new Tiger();
}

0

ยังไม่ได้ลอง แต่ใช้ไม่ได้เหรอ?

YourPageType myPage = (YourPageType)yourControl.Page;

1
ใช่มันไม่ ฉันแค่อยากจะพยายามหลีกเลี่ยงการแคสต์ไปทั่วทุกที่
Kyle Sletten

0

ใช่. มีหลายวิธีในการดำเนินการและนี่เป็นเพียงทางเลือกเดียว:

คุณสามารถทำให้เพจของคุณใช้อินเทอร์เฟซที่กำหนดเองซึ่งแสดงวิธีการที่เรียกว่า "GetContext" หรือบางอย่างและส่งกลับข้อมูลเฉพาะของคุณ จากนั้นการควบคุมของคุณสามารถขอเพจและส่ง:

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

จากนั้นคุณสามารถใช้บริบทนั้นได้ตามต้องการ


0

คุณสามารถเข้าถึงเพจของคุณได้จากการควบคุมใด ๆ โดยการเดินขึ้นไปบนแผนผังหลัก นั่นคือ

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

* ไม่ได้รวบรวมหรือทดสอบ

หรือรับเพจหลักในบริบทปัจจุบัน (ขึ้นอยู่กับเวอร์ชันของคุณ)


สิ่งที่ฉันชอบทำก็คือฉันสร้างอินเทอร์เฟซที่มีฟังก์ชันที่ฉันต้องการใช้ในการควบคุม (เช่น IHostingPage)

จากนั้นฉันส่งหน้าหลัก 'IHostingPage host = (IHostingPage) Parent;' และฉันพร้อมที่จะเรียกใช้ฟังก์ชันบนหน้าที่ฉันต้องการจากการควบคุมของฉัน


0

ฉันจะทำในลักษณะนี้:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.