เป็นไปได้ไหมที่จะลบล้างเมธอดที่ไม่ใช่เสมือนจริง?


92

มีวิธีใดในการลบล้างเมธอดที่ไม่ใช่เสมือนจริงหรือไม่? หรือสิ่งที่ให้ผลลัพธ์ที่คล้ายกัน (นอกเหนือจากการสร้าง method ใหม่เพื่อเรียก method ที่ต้องการ)?

ฉันต้องการลบล้างวิธีการจากMicrosoft.Xna.Framework.Graphics.GraphicsDeviceโดยคำนึงถึงการทดสอบหน่วย


8
คุณหมายถึงoverloadหรือoverride ? Overload = เพิ่มเมธอดที่มีชื่อเดียวกัน แต่พารามิเตอร์ต่างกัน (เช่นโอเวอร์โหลดของ Console.WriteLine ต่างกัน) Override = (คร่าวๆ) เปลี่ยนพฤติกรรมเริ่มต้นของเมธอด (เช่นเมธอด Shape.Draw ซึ่งมีลักษณะการทำงานที่แตกต่างกันสำหรับ Circle, Rectangle ฯลฯ ) คุณสามารถโอเวอร์โหลดเมธอดในคลาสที่ได้รับมาได้เสมอ แต่การลบล้างจะใช้ได้กับเมธอดเสมือนเท่านั้น
itowlson

คำตอบ:


112

ไม่คุณไม่สามารถแทนที่เมธอดที่ไม่ใช่เสมือนได้ สิ่งที่ใกล้เคียงที่สุดที่คุณสามารถทำได้คือซ่อนวิธีการโดยการสร้างnewเมธอดที่มีชื่อเดียวกัน แต่ไม่แนะนำให้ทำเช่นนี้เนื่องจากเป็นการทำลายหลักการออกแบบที่ดี

แต่ถึงแม้การซ่อนเมธอดจะไม่ทำให้คุณมีเวลาในการประมวลผลการเรียกใช้เมธอดที่หลากหลายเหมือนการเรียกเมธอดเสมือนจริง ลองพิจารณาตัวอย่างนี้:

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");
    }
}

ในตัวอย่างนี้ทั้งเรียกไปพิมพ์วิธีการM Foo.Mดังที่คุณเห็นวิธีนี้ช่วยให้คุณสามารถใช้งานเมธอดใหม่ได้ตราบใดที่การอ้างอิงถึงอ็อบเจ็กต์นั้นเป็นประเภทที่ได้รับที่ถูกต้อง แต่การซ่อนเมธอดพื้นฐานจะทำให้ความหลากหลายแตกต่างกัน

ฉันอยากจะแนะนำว่าอย่าซ่อนเมธอดพื้นฐานในลักษณะนี้

ฉันมักจะเข้าข้างผู้ที่ชื่นชอบพฤติกรรมเริ่มต้นของ C # ว่าวิธีการไม่ใช่เสมือนโดยค่าเริ่มต้น (ซึ่งตรงข้ามกับ Java) ฉันจะไปให้ไกลกว่านั้นและบอกว่าชั้นเรียนควรปิดผนึกโดยค่าเริ่มต้นด้วย การสืบทอดเป็นเรื่องยากที่จะออกแบบให้เหมาะสมและความจริงที่ว่ามีวิธีการที่ไม่ถูกทำเครื่องหมายให้เป็นเสมือนแสดงว่าผู้เขียนวิธีนั้นไม่เคยตั้งใจให้วิธีการนั้นถูกแทนที่

แก้ไข: "การจัดส่งแบบกำหนดเวลาดำเนินการ" :

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

ถ้าฉันจะเรียกb.Fooในกรณีนั้น CLR จะกำหนดประเภทของวัตถุที่การbอ้างอิงชี้ไปอย่างถูกต้องBarและจะส่งการเรียกไปMอย่างเหมาะสม


6
แม้ว่า "การจัดส่งแบบกำหนดเวลาดำเนินการ" จะเป็นวิธีที่ถูกต้องในทางเทคนิคในการพูด แต่ฉันคิดว่าสิ่งนี้อาจจะอยู่เหนือหัวของเกือบทุกคน!
Orion Edwards

3
แม้ว่าจะเป็นเรื่องจริงที่ผู้เขียนอาจตั้งใจที่จะไม่อนุญาตให้ใช้วิธีการที่ลบล้าง แต่ก็ไม่เป็นความจริงที่จำเป็นต้องทำอย่างถูกต้อง ฉันรู้สึกว่าทีม XNA ควรติดตั้งอินเทอร์เฟซ IGraphicsDevice เพื่อให้ผู้ใช้มีความยืดหยุ่นมากขึ้นในการดีบักและการทดสอบหน่วย ฉันถูกบังคับให้ทำสิ่งที่น่าเกลียดมากและสิ่งนี้ควรได้รับการคาดการณ์โดยทีมงาน สามารถดูการสนทนาเพิ่มเติมได้ที่นี่: forums.xna.com/forums/p/30645/226222.aspx
zfedoran

2
@Orion ฉันก็ไม่เข้าใจเหมือนกัน แต่หลังจากใช้ Google อย่างรวดเร็วฉันก็รู้สึกยินดีที่ได้เห็นการใช้คำที่ "ถูกต้อง"
zfedoran

10
ฉันไม่ซื้ออาร์กิวเมนต์ "ผู้เขียนไม่ได้ตั้งใจให้" เลย เหมือนกับการบอกว่าผู้ประดิษฐ์ล้อไม่ได้คาดหวังว่าล้อจะถูกใส่ในรถยนต์ดังนั้นเราจึงไม่สามารถใช้กับรถยนต์ได้ ฉันรู้ว่าวงล้อ / วิธีการทำงานอย่างไรและฉันต้องการทำสิ่งที่แตกต่างออกไปเล็กน้อย ฉันไม่สนใจว่าผู้สร้างต้องการให้ฉันหรือไม่ ขออภัยที่พยายามรื้อฟื้นกระทู้ที่ตายแล้ว ฉันแค่ต้องเป่าไอน้ำก่อนที่ฉันจะหาวิธีที่ไม่สะดวกในการแก้ไขปัญหานี้ฉันอยู่ :)
เจฟฟ์

2
แล้วเวลาที่คุณสืบทอด codebase ขนาดใหญ่และจำเป็นต้องเพิ่มการทดสอบสำหรับฟังก์ชันการทำงานใหม่ แต่เพื่อทดสอบว่าคุณต้องสร้างอินสแตนซ์ของเครื่องจักรจำนวนมาก ใน Java ฉันแค่ขยายส่วนเหล่านั้นและแทนที่เมธอดที่ต้องการด้วยต้นขั้ว ใน C # หากวิธีการไม่ถูกทำเครื่องหมายเป็นเสมือนฉันต้องหากลไกอื่น ๆ (การเยาะเย้ยไลบรารีหรืออย่างนั้นและถึงอย่างนั้น)
Adam Parkin

22

ไม่คุณทำไม่ได้

คุณสามารถแทนที่วิธีการเสมือนเท่านั้น - ดูMSDN ที่นี่ :

ใน C # คลาสที่ได้รับสามารถมีเมธอดที่มีชื่อเดียวกับเมธอดคลาสพื้นฐาน

  • ต้องกำหนดเมธอดคลาสฐานเสมือน

6

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


3

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

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

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


2

คุณไม่สามารถแทนที่เมธอดที่ไม่ใช่เสมือนของคลาสใด ๆ ใน C # (โดยไม่ต้องแฮ็ก CLR) แต่คุณสามารถแทนที่วิธีการใด ๆ ของอินเทอร์เฟซที่คลาสใช้ ถือว่าเราไม่มีการปิดผนึก

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();
    }
}

และนี่คือการสาธิต

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();

    }
}

@ แดนนี่คือการสาธิตสด: dotnetfiddle.net/VgRwKK
Vyacheslav Napadovsky

0

ในกรณีที่คุณรับช่วงจากคลาสที่ไม่ได้มาคุณสามารถสร้างคลาสซูเปอร์ที่เป็นนามธรรมและสืบทอดจากดาวน์สตรีมแทนได้


0

มีวิธีใดในการลบล้างเมธอดที่ไม่ใช่เสมือนจริงหรือไม่? หรือสิ่งที่ให้ผลลัพธ์ที่คล้ายกัน (นอกเหนือจากการสร้าง method ใหม่เพื่อเรียก method ที่ต้องการ)?

คุณไม่สามารถแทนที่เมธอดที่ไม่ใช่เสมือน อย่างไรก็ตามคุณสามารถใช้newคีย์เวิร์ดตัวปรับแต่งเพื่อให้ได้ผลลัพธ์ที่คล้ายกัน:

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

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

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

หากตัวแก้ไขการเข้าถึงไม่เหมือนกัน:

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());

... เมื่อเทียบกับถ้าปรับปรุงการเข้าถึงเป็นเหมือนเดิม:

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());

ดังที่ได้กล่าวไว้ในคำตอบก่อนหน้านี้ไม่ใช่หลักการออกแบบที่ดี


-5

มีวิธีการบรรลุสิ่งนี้โดยใช้คลาสนามธรรมและวิธีนามธรรม

พิจารณา

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

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

ตอนนี้หากคุณต้องการมี method MethodToBeTested () ที่แตกต่างกันให้เปลี่ยน Class Base เป็นคลาสนามธรรมและ Method MethodToBeTested () เป็นวิธีนามธรรม

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

ด้วยโมฆะนามธรรม MethodToBeTested () มีปัญหา; การใช้งานหายไป

ดังนั้นสร้างclass DefaultBaseImplementation : Baseเพื่อให้มีการใช้งานเริ่มต้น

และสร้างอีกรายการclass UnitTestImplementation : Baseเพื่อให้มีการใช้งานการทดสอบหน่วย

ด้วยคลาสใหม่ 2 คลาสนี้ฟังก์ชันการทำงานของคลาสพื้นฐานสามารถถูกลบล้างได้

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

}

Class UnitTestImplementation : Base
{

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

}

ตอนนี้คุณมี 2 คลาสการใช้ MethodToBeTested()(เอาชนะ)

คุณสามารถสร้างอินสแตนซ์คลาส (ที่ได้มา) ได้ตามต้องการ (เช่นด้วยการใช้งานพื้นฐานหรือการใช้การทดสอบหน่วย)


@slavoo: สวัสดี slavoo ขอบคุณสำหรับการอัปเดตรหัส แต่คุณช่วยบอกเหตุผลในการลดคะแนนได้ไหม
ShivanandSK

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