Có thể ghi đè một phương thức không phải ảo không?


90

Có cách nào để ghi đè một phương thức 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)?

Tôi muốn ghi đè một phương pháp Microsoft.Xna.Framework.Graphics.GraphicsDevicecó lưu ý đến thử nghiệm đơn vị.


8
Bạn có nghĩa là quá tải hoặc ghi đè ? Overload = thêm một phương thức có cùng tên nhưng các tham số khác nhau (ví dụ: quá tải khác nhau của Console.WriteLine). Override = (đại khái) 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 đối với Hình tròn, Hình chữ nhật, v.v.). Bạn luôn có thể quá tải một phương pháp trong một lớp học có nguồn gốc, nhưng trọng chỉ áp dụng cho phương pháp ảo.
itowlson

Câu trả lời:


111

Không, bạn không thể ghi đè một 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 một newphương thức có cùng tên nhưng điều này không được khuyến khích vì nó phá vỡ các nguyên tắc thiết kế tốt.

Nhưng ngay cả việc ẩn một phương thức cũng không cung cấp cho bạn thời gian thực thi việc gửi đa hình các lệnh gọi phương thức giống như một lệnh 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 đều gọi Mphương thức print 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 đó có kiểu dẫn xuất chính xác nhưng ẩn một phương thức cơ sở thì không. phá vỡ tính đa hình.

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 đứng về phía 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 (trái ngược với Java). Tôi thậm chí còn đi xa hơn và nói rằng các lớp cũng nên được niêm phong theo mặc định. Việc kế thừa rất khó để thiết kế cho phù hợp và thực tế là có một phương thức không được đánh dấu là ảo cho thấy rằng tác giả của phương pháp đó không bao giờ có ý định ghi đè phương thức.

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

Ý tôi là đây là hành vi mặc định xảy ra tại thời điểm thực thi khi bạn gọi các phương thức ảo. Hãy nói ví dụ rằng trong ví dụ mã trước của tôi, thay vì xác định một phương thức không phải ảo, trên thực tế, tôi đã xác định một phương thức ảo và một phương thức ghi đè thực sự.

Nếu tôi gọi b.Footrong trường hợp đó, CLR sẽ xác định chính xác loại đối tượng mà btham chiếu trỏ đến Barvà sẽ gửi lệnh gọi đến Mmột cách thích hợp.


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

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

2
@Orion Tôi cũng không hiểu nhưng sau khi google nhanh, tôi đánh giá cao việc sử dụng các thuật ngữ "đúng".
zfedoran

9
Tôi không mua lập luận "tác giả không có ý định cho nó" ở tất cả. Điều đó giống như nói rằng người phát minh ra bánh xe đã không lường trước được việc bánh xe sẽ được đưa vào ô tô nên chúng ta không thể sử dụng chúng trên ô tô. Tôi biết bánh xe / phương pháp hoạt động như thế nào và tôi muốn làm điều gì đó hơi khác với nó. Tôi không quan tâm liệu người sáng tạo có muốn tôi hay không. Xin lỗi vì đã cố gắng hồi sinh một chuỗi đã chết. Tôi chỉ cần đòn của một số hơi nước trước khi tôi con số ra một cách nào đó thực sự bất tiện để có được xung quanh vấn đề này tôi đang ở :)
Jeff

2
Vì vậy, còn khi bạn kế thừa một cơ sở mã lớn và cần thêm một bài kiểm tra cho chức năng mới, nhưng để kiểm tra bạn cần khởi tạo toàn bộ máy móc. Trong Java, tôi chỉ mở rộng các phần đó và ghi đè các phương thức được yêu cầu với các phần sơ khai. Trong C #, nếu các phương thức không được đánh dấu là ảo, tôi phải tìm một số cơ chế khác (thư viện chế nhạo hoặc tương tự, và thậm chí sau đó).
Adam Parkin

22

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

Bạn chỉ có thể ghi đè một phương thức ảo - xem MSDN tại đây :

Trong C #, các lớp dẫn xuất có thể chứa các phương thức trùng tên với các phương thức của lớp cơ sở.

  • Phương thức lớp cơ sở phải được định nghĩa ảo.

6

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


3

Tôi nghĩ rằng bạn đang nhầm lẫn quá tải và ghi đè, 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 bộ tham số khác nhau trong khi ghi đè có nghĩa là bạn có một triển khai khác cho một phương thức trong lớp dẫn xuất (do đó thay thế hoặc sửa đổi hành vi trong lớp cơ sở của nó).

Nếu một phương thức là ảo, bạn có thể ghi đè nó bằng từ khóa override trong lớp derrived. Tuy nhiên, các phương pháp không ảo chỉ có thể ẩn việc triển khai cơ sở bằng cách sử dụng từ khóa mới thay cho từ khóa ghi đè. Tuyến không ảo sẽ vô dụng nếu người gọi truy cập phương thức thông qua một biến được nhập là kiểu cơ sở vì trình biên dịch sẽ sử dụng một điều phối tĩnh đến phương thức cơ sở (có nghĩa là mã trong lớp derrived 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 của bạn mới có thể truy cập nó.


2

Bạn không thể ghi đè phương thức không phải ảo của bất kỳ lớp nào trong C # (không hack CLR), nhưng bạn có thể ghi đè bất kỳ phương thức nào của giao diện mà lớp thực hiện. Hãy xem xét chúng tôi có không niêm phong

class GraphicsDevice: IGraphicsDevice {
    public void DoWork() {
        Console.WriteLine("GraphicsDevice.DoWork()");
    }
}

// with its interface
interface IGraphicsDevice {
    void DoWork();
}

// You can't just override DoWork in a child class,
// but if you replace usage of GraphicsDevice to IGraphicsDevice,
// then you can override this method (and, actually, the whole interface).

class MyDevice: GraphicsDevice, IGraphicsDevice {
    public new void DoWork() {
        Console.WriteLine("MyDevice.DoWork()");
        base.DoWork();
    }
}

Và đây là bản demo

class Program {
    static void Main(string[] args) {

        IGraphicsDevice real = new GraphicsDevice();
        var myObj = new MyDevice();

        // demo that interface override works
        GraphicsDevice myCastedToBase = myObj;
        IGraphicsDevice my = myCastedToBase;

        // obvious
        Console.WriteLine("Using real GraphicsDevice:");
        real.DoWork();

        // override
        Console.WriteLine("Using overriden GraphicsDevice:");
        my.DoWork();

    }
}

@Dan đây là bản demo trực tiếp: dotnetfiddle.net/VgRwKK
Vyacheslav Napadovsky

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 siêu lớp trừu tượng và kế thừa từ nó xuống dòng thay thế.


0

Có cách nào để ghi đè một phương thức 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 đè một phương thức không phải ảo. Tuy nhiên bạn có thể sử dụng newtừ khóa bổ trợ để nhận đượ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 đảm bảo rằng công cụ sửa đổi quyền truy cập cũng giống nhau, nếu không bạn sẽ không nhận được kế thừa. Nếu khác kế thừa lớp từ Class1các newtừ khóa trongClass1 sẽ không ảnh hưởng đến đối tượng kế thừa từ nó, trừ trường hợp sửa đổi lần truy cập là như nhau.

Nếu công cụ sửa đổi quyền truy cập không 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 công cụ sửa đổi truy cập như 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.


-5

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 thức trừu tượng.

Xem xét

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

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

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

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

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

Do đó tạo ra một class DefaultBaseImplementation : Base để có triển khai mặc định.

Và tạo một class UnitTestImplementation : Base để triển khai unit test.

Với 2 lớp mới này, chức năng của lớp cơ sở có thể được 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 triển khai (ghi đè) MethodToBeTested() .

Bạn có thể khởi tạo lớp (dẫn xuất) theo yêu cầu (tức là với triển khai cơ sở hoặc với triển khai kiểm tra đơn vị).


@slavoo: Chào slavoo. Cảm ơn vì đã cập nhật mã. Nhưng bạn có thể vui lòng cho tôi biết lý do từ chối điều này không?
ShivanandSK

6
Bởi vì nó không trả lời câu hỏi. Anh ấy đang hỏi liệu bạn có thể ghi đè các thành viên không được đánh dấu ả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à tóm tắt.
Lee Louviere
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.