2009-12-05 78 views
65

Có cách nào để ghi đè lên một phương pháp phi ảo không? hoặc cái gì đó cho kết quả tương tự (ngoài việc tạo một phương thức mới để gọi phương thức mong muốn)?Có thể ghi đè phương thức không phải ảo không?

Tôi muốn ghi đè phương thức từ Microsoft.Xna.Framework.Graphics.GraphicsDevice với thử nghiệm đơn vị trong đầu.

+7

Bạn có nghĩa là * quá tải * hoặc * ghi đè * không? Quá tải = thêm phương thức có cùng tên nhưng các thông số khác nhau (ví dụ: các quá tải khác nhau của Console.WriteLine). Ghi đè = (gần) thay đổi hành vi mặc định của phương thức (ví dụ: phương thức Shape.Draw có hành vi khác nhau cho Vòng kết nối, Hình chữ nhật, v.v.). Bạn luôn có thể * quá tải * một phương thức trong lớp dẫn xuất, nhưng * ghi đè * chỉ áp dụng cho các phương thức ảo. – itowlson

Trả lời

81

Không, bạn không thể ghi đè phương thức không phải ảo. Điều gần nhất bạn có thể làm là ẩn phương thức bằng cách tạo phương thức new có cùng tên nhưng điều này không được khuyến khích vì nó phá vỡ nguyên tắc thiết kế tốt.

Nhưng thậm chí ẩn một phương thức sẽ không cung cấp cho bạn thời gian thực hiện gửi đa hình các cuộc gọi phương thức như cuộc gọi phương thức ảo thực sự. Hãy xem xét ví dụ này:

using System; 

class Example 
{ 
    static void Main() 
    { 
     Foo f = new Foo(); 
     f.M(); 

     Foo b = new Bar(); 
     b.M(); 
    } 
} 

class Foo 
{ 
    public void M() 
    { 
     Console.WriteLine("Foo.M"); 
    } 
} 

class Bar : Foo 
{ 
    public new void M() 
    { 
     Console.WriteLine("Bar.M"); 
    } 
} 

Trong ví dụ này cả hai gọi tới phương thức M in Foo.M. Như bạn có thể thấy cách tiếp cận này cho phép bạn có một triển khai mới cho một phương thức miễn là tham chiếu đến đối tượng đó thuộc loại có nguồn gốc chính xác nhưng ẩn phương thức cơ sở thì đa hình phá vỡ.

Tôi khuyên bạn không nên ẩn các phương thức cơ sở theo cách này.

Tôi có xu hướng phụ thuộc vào những người ủng hộ hành vi mặc định của C# rằng các phương thức không phải là ảo theo mặc định (ngược với Java). Tôi sẽ đi xa hơn và nói rằng các lớp học cũng nên được niêm phong theo mặc định. Thừa kế là khó để thiết kế cho đúng và thực tế là có một phương pháp mà không được đánh dấu là ảo chỉ ra rằng tác giả của phương pháp đó không bao giờ có ý định cho phương pháp được ghi đè.

Chỉnh sửa: "thời gian thực hiện đa hình văn":

Những gì tôi có ý nghĩa bởi đây là hành vi mặc định điều đó xảy ra vào thời điểm thực hiện khi bạn gọi phương pháp ảo. Ví dụ, trong ví dụ mã trước của tôi, thay vì định nghĩa một phương thức không phải ảo, thực tế tôi đã định nghĩa một phương thức ảo và một phương thức ghi đè thực sự.

Nếu tôi được gọi b.Foo trong trường hợp đó, CLR sẽ xác định một cách chính xác các loại đối tượng mà b điểm tham chiếu đến như Bar và sẽ cử các cuộc gọi đến M một cách thích hợp.

+4

Mặc dù "công văn đa hình thời gian thực hiện" về mặt kỹ thuật là cách chính xác để nói nó, tôi tưởng tượng điều này có thể đi qua đầu của hầu hết mọi người! –

+1

Mặc dù đúng là tác giả có thể có ý định không cho phép ghi đè phương pháp là không đúng sự thật rằng nó nhất thiết phải là điều chính xác để làm. Tôi cảm thấy rằng đội XNA nên đã triển khai một giao diện IGraphicsDevice để cho phép người dùng linh hoạt hơn trong việc gỡ rối và kiểm thử đơn vị. Tôi buộc phải làm một số điều rất xấu và điều này nên được nhóm nghiên cứu dự đoán. Thảo luận thêm có thể được tìm thấy ở đây: http://forums.xna.com/forums/p/30645/226222.aspx – zfedoran

+0

@Orion - đủ công bằng, tôi đã thêm một lời giải thích :) –

17

Không, bạn không thể.

Bạn chỉ có thể ghi đè lên một phương pháp ảo - xem MSDN here:

Trong C#, các lớp học có nguồn gốc có thể chứa các phương pháp với tên giống như các phương pháp lớp cơ sở.

  • Phương pháp lớp cơ sở phải được xác định ảo.
4

Nếu lớp cơ sở không bị phong ấn sau đó bạn có thể kế thừa từ nó và viết một phương pháp mới mà giấu đi những cơ sở một (sử dụng từ khóa "mới" trong tờ khai phương pháp). Nếu không, bạn không thể ghi đè nó vì nó không bao giờ là tác giả ban đầu có ý định để nó bị ghi đè, do đó tại sao nó không phải là ảo.

3

Tôi nghĩ rằng bạn đang bị quá tải và đè bẹp lẫn lộn, quá tải có nghĩa là bạn có hai hoặc nhiều phương thức có cùng tên nhưng các tham số khác nhau trong khi ghi đè có nghĩa là bạn có triển khai khác nhau cho một phương thức trong lớp dẫn xuất. hoặc sửa đổi hành vi trong lớp cơ sở của nó).

Nếu phương pháp là ảo, bạn có thể ghi đè phương thức đó bằng cách sử dụng từ khóa ghi đè trong lớp bị loại. Tuy nhiên, các phương pháp không phải ảo chỉ có thể ẩn cài đặt cơ sở bằng cách sử dụng từ khóa mới thay cho từ khóa ghi đè. Route không ảo là vô ích nếu người gọi truy cập phương thức thông qua một biến được gõ là kiểu cơ sở vì trình biên dịch sẽ sử dụng một công văn tĩnh đến phương thức cơ sở (có nghĩa là mã trong lớp bị loại của bạn sẽ không bao giờ được gọi).

Không bao giờ có bất cứ điều gì ngăn cản bạn thêm quá tải vào một lớp hiện có, nhưng chỉ có mã biết về lớp học của bạn mới có thể truy cập nó.

-4

Có một cách để đạt được điều này bằng cách sử dụng lớp trừu tượng và phương pháp trừu tượng.

Cân nhắc

Class Base 
{ 
    void MethodToBeTested() 
    { 
     ... 
    } 

    void Method1() 
    { 
    } 

    void Method2() 
    { 
    } 

    ... 
} 

Bây giờ, nếu bạn muốn có phiên bản khác nhau của phương pháp MethodToBeTested(), sau đó thay đổi lớp cơ sở để một lớp trừu tượng và phương pháp MethodToBeTested() là một phương pháp trừu tượng

abstract Class Base 
{ 

    abstract void MethodToBeTested(); 

    void Method1() 
    { 
    } 

    void Method2() 
    { 
    } 

    ... 
} 

Với khoảng trống trừu tượng MethodToBeTested() có vấn đề; việc triển khai đã biến mất.

Do đó, hãy tạo class DefaultBaseImplementation : Base để thực hiện mặc định.

Và tạo một class UnitTestImplementation : Base khác để thực hiện kiểm tra đơn vị.

Với 2 lớp mới này, chức năng lớp cơ sở có thể bị ghi đè.

Class DefaultBaseImplementation : Base  
{ 
    override void MethodToBeTested()  
    {  
     //Base (default) implementation goes here  
    } 

} 

Class UnitTestImplementation : Base 
{ 

    override void MethodToBeTested()  
    {  
     //Unit test implementation goes here  
    } 

} 

Bây giờ bạn có 2 lớp thực hiện (ghi đè) MethodToBeTested().

Bạn có thể khởi tạo lớp (có nguồn gốc) theo yêu cầu (tức là với việc triển khai cơ sở hoặc thực hiện kiểm tra đơn vị).

+0

@slavoo: Xin chào slavoo. Cảm ơn bạn đã cập nhật mã. Nhưng bạn có thể vui lòng cho tôi biết lý do để downvoting cái này không? – ShivanandSK

+3

Bởi vì nó không trả lời câu hỏi. Anh ấy hỏi bạn có thể ghi đè lên các thành viên không được đánh dấu là ảo hay không. Bạn đã chứng minh rằng bạn phải triển khai các thành viên được đánh dấu là trừu tượng. –

0

Trong trường hợp bạn đang kế thừa từ một lớp không có nguồn gốc, bạn có thể chỉ cần tạo một lớp siêu trừu tượng và kế thừa từ nó ở hạ lưu thay thế.

0

Có cách nào để ghi đè phương pháp không phải ảo không? hoặc cái gì đó cho kết quả tương tự (ngoài việc tạo một phương thức mới để gọi phương thức mong muốn)?

Bạn không thể ghi đè phương thức không phải ảo. Tuy nhiên bạn có thể sử dụng từ khóa new sửa đổi để có được kết quả tương tự:

class Class0 
{ 
    public int Test() 
    { 
     return 0; 
    } 
} 

class Class1 : Class0 
{ 
    public new int Test() 
    { 
     return 1; 
    } 
} 
. . . 
// result of 1 
Console.WriteLine(new Class1().Test()); 

Bạn cũng sẽ muốn chắc chắn rằng access modifier cũng là như nhau, nếu không bạn sẽ không nhận thừa kế xuống dòng.Nếu một lớp khác kế thừa từ Class1 từ khóa new trong Class1 sẽ không ảnh hưởng đến các đối tượng thừa kế từ nó, trừ khi công cụ sửa đổi truy cập giống nhau.

Nếu sửa đổi lần truy cập không phải là giống nhau:

class Class0 
{ 
    protected int Test() 
    { 
     return 0; 
    } 
} 

class Class1 : Class0 
{ 
    // different access modifier 
    new int Test() 
    { 
     return 1; 
    } 
} 

class Class2 : Class1 
{ 
    public int Result() 
    { 
     return Test(); 
    } 
} 
. . . 
// result of 0 
Console.WriteLine(new Class2().Result()); 

... so với nếu sửa đổi lần truy cập giống nhau:

class Class0 
{ 
    protected int Test() 
    { 
     return 0; 
    } 
} 

class Class1 : Class0 
{ 
    // same access modifier 
    protected new int Test() 
    { 
     return 1; 
    } 
} 

class Class2 : Class1 
{ 
    public int Result() 
    { 
     return Test(); 
    } 
} 
. . . 
// result of 1 
Console.WriteLine(new Class2().Result()); 

Như đã chỉ ra trong một câu trả lời trước , đây không phải là một nguyên tắc thiết kế tốt.

Các vấn đề liên quan