C # có hỗ trợ hiệp phương sai kiểu trả về không?


84

Tôi đang làm việc với .NET framework và tôi thực sự muốn có thể tạo một loại trang tùy chỉnh mà tất cả trang web của tôi đều sử dụng. Sự cố xảy ra khi tôi đang cố gắng truy cập trang từ một điều khiển. Tôi muốn có thể trả về loại trang cụ thể của mình thay vì trang mặc định. Có cách nào để làm điều này?

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

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

Câu trả lời:


169

CẬP NHẬT: Câu trả lời này được viết vào năm 2011. Sau hai thập kỷ mọi người đề xuất hiệp phương sai kiểu trả về cho C #, có vẻ như nó cuối cùng sẽ được thực hiện; Tôi khá ngạc nhiên. Xem phần cuối của https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ để biết thông báo; Tôi chắc chắn chi tiết sẽ theo sau.


Có vẻ như những gì bạn muốn là hiệp phương sai kiểu trả về. C # không hỗ trợ hiệp phương sai kiểu trả về.

Hiệp phương sai kiểu trả về là nơi bạn ghi đè phương thức lớp cơ sở trả về kiểu ít cụ thể hơn với phương thức trả về kiểu cụ thể hơn:

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

Điều này là an toàn vì người tiêu dùng Nội dung thông qua Bao vây mong đợi một Động vật và Thủy cung hứa hẹn không chỉ đáp ứng yêu cầu đó mà hơn thế nữa, thực hiện một lời hứa nghiêm ngặt hơn: rằng động vật luôn là cá.

Loại hiệp phương sai này không được hỗ trợ trong C # và không bao giờ được hỗ trợ. Nó không được hỗ trợ bởi CLR. (Nó được hỗ trợ bởi C ++ và bởi việc triển khai C ++ / CLI trên CLR; nó làm như vậy bằng cách tạo ra các phương thức trợ giúp kỳ diệu thuộc loại mà tôi đề xuất bên dưới.)

(Một số ngôn ngữ cũng hỗ trợ sự đối nghịch kiểu tham số chính thức - bạn có thể ghi đè phương thức lấy Cá bằng phương thức lấy Động vật. Một lần nữa, hợp đồng được thực hiện; lớp cơ sở yêu cầu mọi Cá được xử lý và dẫn xuất lớp hứa hẹn không chỉ xử lý cá, mà còn bất kỳ động vật nào. Tương tự, C # và CLR không hỗ trợ sự đối nghịch kiểu tham số chính thức.)

Cách bạn có thể khắc phục hạn chế này là làm một số việc như:

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() { ... }
}

Giờ đây, bạn nhận được cả lợi ích của việc ghi đè một phương thức ảo và gõ mạnh hơn khi sử dụng Aquarium loại thời gian biên dịch.


3
Hoàn hảo! Tôi quên mất việc trốn!
Kyle Sletten

3
câu trả lời tốt như mọi khi. Chúng tôi đã bỏ lỡ những câu trả lời đó trong một thời gian.
Dhananjay,

4
Có lý do nào cho việc không hỗ trợ hiệp phương sai trả về và hiệp phương sai có sẵn để đọc ở bất cứ đâu không?
porges

26
@EricLippert Là một nhóm nhạc C #, tôi luôn tò mò về "hậu trường", vì vậy tôi chỉ cần hỏi: Phương sai kiểu trả về có từng được xem xét cho C # và được coi là có ROI quá thấp không? Tôi hiểu những hạn chế về thời gian và bugdet, nhưng từ tuyên bố "không bao giờ được hỗ trợ" của bạn, có vẻ như nhóm thiết kế thực sự KHÔNG muốn có điều này trong ngôn ngữ. Ở phía bên kia của hàng rào, Java đã giới thiệu tính năng ngôn ngữ này trong Java 5, vì vậy tôi tự hỏi sự khác biệt là gì trong các nguyên tắc lớn hơn, bao quát chi phối quá trình thiết kế ngôn ngữ.
Cristian Diaconescu,

7
@Cyral: Đúng; nó nằm trong danh sách trong nhóm "lãi suất vừa phải" - nơi mà nó đã tồn tại trong một thời gian dài! Nó có thể xảy ra, nhưng tôi khá nghi ngờ.
Eric Lippert

8

Với các giao diện, tôi đã giải quyết nó bằng cách triển khai rõ ràng giao diện:

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

2

Đặt cái này vào đối tượng MyControl sẽ hoạt động:

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

Bạn không thể ghi đè thuộc tính vì nó trả về một kiểu khác ... nhưng bạn có thể xác định lại nó.

Bạn không cần hiệp phương sai trong ví dụ này, vì nó tương đối đơn giản. Tất cả những gì bạn đang làm là kế thừa đối tượng cơ sở Pagetừ đó MyPage. Bất kỳ thứ Controlgì bạn muốn trả lại MyPagethay vì Pagecần phải xác định lại Pagetài sản củaControl


1

Có, nó hỗ trợ hiệp phương sai, nhưng nó phụ thuộc vào điều chính xác mà bạn đang cố gắng đạt được.

Tôi cũng có xu hướng sử dụng generic rất nhiều cho mọi thứ, có nghĩa là khi bạn làm điều gì đó như:

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

}

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

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

Và không "mất" các loại của bạn.


1

Đây là một tính năng cho C # 9.0 (.Net 5) sắp tới mà bạn có thể tải xuống phiên bản xem trước ngay bây giờ.

Mã sau hiện xây dựng thành công (không cho 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

Tôi đã không thử nó, nhưng nó không hoạt động?

YourPageType myPage = (YourPageType)yourControl.Page;

1
Đúng vậy. Tôi chỉ muốn thử và tránh đi casting khắp nơi.
Kyle Sletten

0

Đúng. Có nhiều cách để thực hiện việc này và đây chỉ là một lựa chọn:

Bạn có thể làm cho trang của mình triển khai một số giao diện tùy chỉnh để hiển thị một phương thức gọi là "GetContext" hoặc một thứ gì đó và nó trả về thông tin cụ thể của bạn. Sau đó, kiểm soát của bạn có thể chỉ cần yêu cầu trang và truyền:

var myContextPage = this.Page as IMyContextGetter;

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

Sau đó, bạn có thể sử dụng ngữ cảnh đó theo cách bạn muốn.


0

Bạn có thể truy cập trang của mình từ bất kỳ điều khiển nào bằng cách đi lên cây mẹ. Đó là

myParent = this;

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

* Không biên dịch hoặc thử nghiệm.

Hoặc lấy trang mẹ trong ngữ cảnh hiện tại (tùy thuộc vào phiên bản của bạn).


Sau đó, những gì tôi muốn làm là thế này: Tôi tạo một giao diện với các chức năng tôi muốn sử dụng trong điều khiển (ví dụ: IHostingPage)

Sau đó, tôi truyền trang mẹ 'IHostingPage host = (IHostingPage) Parent;' và tôi đã sẵn sàng để gọi hàm trên trang mà tôi cần từ sự kiểm soát của mình.


0

Tôi sẽ làm theo cách này:

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; } }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.