ฉันจะใช้วิธีการแบบคงที่บนอินเทอร์เฟซได้อย่างไร


92

ฉันมี C ++ DLL ของบุคคลที่สามที่ฉันเรียกจาก C #

วิธีการเป็นแบบคงที่

ฉันต้องการสรุปเพื่อทำการทดสอบหน่วยดังนั้นฉันจึงสร้างอินเทอร์เฟซด้วยวิธีการแบบคงที่ แต่ตอนนี้ข้อผิดพลาดของโปรแกรมของฉันกับ:

ตัวปรับแต่ง 'คงที่' ไม่ถูกต้องสำหรับรายการนี้

MyMethod cannot be accessed with an instance reference; qualify it with a type name instead

ฉันจะบรรลุนามธรรมนี้ได้อย่างไร

รหัสของฉันมีลักษณะเช่นนี้

private IInterfaceWithStaticMethods MyInterface;

public MyClass(IInterfaceWithStaticMethods myInterface)
{
  this.MyInterface = myInterface;
}

public void MyMethod()
{
  MyInterface.StaticMethod();
}

3
บางทีคุณอาจทำได้ด้วยวิธีการขยาย: stackoverflow.com/questions/1243921/…
hcb

คำตอบ:


47

คุณไม่สามารถกำหนดสมาชิกแบบคงที่บนอินเทอร์เฟซใน C # อินเตอร์เฟซที่เป็นสัญญาสำหรับอินสแตนซ์

ฉันขอแนะนำให้สร้างอินเทอร์เฟซตามที่คุณเป็นอยู่ในขณะนี้ แต่ไม่มีคีย์เวิร์ดแบบคงที่ จากนั้นสร้างคลาสStaticIInterfaceที่ใช้อินเทอร์เฟซและเรียกใช้เมธอด C ++ แบบคงที่ ในการทดสอบหน่วยให้สร้างคลาสอื่นFakeIInterfaceที่ใช้อินเทอร์เฟซเช่นกัน แต่ทำในสิ่งที่คุณต้องการเพื่อจัดการกับการทดสอบหน่วยของคุณ

เมื่อคุณกำหนด 2 คลาสนี้แล้วคุณสามารถสร้างคลาสที่คุณต้องการสำหรับสภาพแวดล้อมของคุณและส่งต่อไปยังคอนMyClassสตรัคเตอร์ของ


65
-1 สำหรับการบอกว่าAn interface is a contract, not an implementation.- นั่นเป็นความจริง แต่ไม่เกี่ยวข้องโดยสิ้นเชิง ( ไม่ใช่ sequitur ) ที่นี่เนื่องจากวิธีการแบบคงที่ไม่ได้เป็นส่วนหนึ่งของการนำไปใช้งานเอง - การใช้งานตามคำจำกัดความจะขึ้นอยู่กับข้อมูลซึ่งในทางกลับกันจะไม่สามารถเข้าถึงได้สำหรับสมาชิกแบบคงที่ An interface type definition can define and implement static methods (see §8.4.3) since static methods are associated with the interface type itself rather than with any value of the type.- จำไว้ว่าstaticสมาชิกที่มักจะมีวิธีการที่ยูทิลิตี้

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

1
ฉันยอมรับบางส่วนว่าAn interface is a contract, not an implementationไม่มีประโยชน์บางครั้งการปรับบริบทเล็กน้อยก็ช่วยได้จริงๆ และฉันเห็นด้วยอย่างยิ่งกับstatic method is not a part of implementation itself วิธีการแบบคงที่มีการนำไปใช้ซึ่งจะกลายเป็นส่วนหนึ่งของการนำไปใช้งานก็ต่อเมื่อใช้เป็นการนำไปใช้ในการนำวิธีอื่นมาใช้ อย่างไรก็ตามพจนานุกรมของฉันขึ้นอยู่กับสิ่งที่เรียนรู้เท่าที่ฉันรู้คำศัพท์อาจแตกต่างกันไปขึ้นอยู่กับภาษาโปรแกรมด้วย วิธีการคงที่ไม่สามารถเป็นอินเทอร์เฟซได้เนื่องจากสามารถใช้งานได้เพียง 1 ครั้งเท่านั้น
CoffeDeveloper

ลองนึกภาพว่าฉันมีIPersonสัญญาที่ระบุว่าGetCountryจะให้ชื่อประเทศต้นทางของบุคคลนั้น ... FrenchPersonเอนทิตีทั้งหมดจะพูดว่า "ฝรั่งเศส" และGermanPersonทั้งหมดจะพูดว่า "เยอรมนี" นอกจากนี้ยังมีประโยชน์เมื่อเอนทิตีประเภทต่างๆใช้ตาราง (ข้อมูล) เดียวกันเช่น MS Azure หนึ่งกล่าวว่าConnection, PostและCommentถูกเก็บไว้ในUsersAzureTable เพื่อให้หน่วยงานที่ต้นไม้มีข้อมูลที่ใช้ร่วมกันIUsersอาจจะมีGetTableNameวิธีการคง ...
เสิร์จ

@vaxquis - IMHO "สัญญา" ที่จะมีความเกี่ยวข้องถ้าประโยคที่ถูก reworded: `อินเตอร์เฟซที่เป็นสัญญาสำหรับอินสแตนซ์ สมาชิกคงเป็นส่วนหนึ่งของประเภท ประโยคที่ใช้ซ้ำนี้บอกว่า (ถูกต้อง) ว่าไม่มีความหมายในสัญญาเช่น ดังนั้นฉันคิดว่าปัญหาเป็นเพียงการใช้ถ้อยคำที่ไม่ชัดเจนไม่ใช่การสืบเนื่อง
ToolmakerSteve

112

อินเทอร์เฟซต้องไม่มีสมาชิกแบบคงที่และไม่สามารถใช้วิธีการแบบคงที่เป็นการใช้วิธีการอินเทอร์เฟซได้

สิ่งที่คุณทำได้คือใช้อินเทอร์เฟซที่ชัดเจน:

public interface IMyInterface
{
    void MyMethod();
}

public class MyClass : IMyInterface
{
    static void MyMethod()
    {
    }

    void IMyInterface.MyMethod()
    {
        MyClass.MyMethod();
    }
}

หรือคุณสามารถใช้วิธีการไม่คงที่แม้ว่าพวกเขาจะไม่เข้าถึงสมาชิกเฉพาะอินสแตนซ์ก็ตาม


18
สำหรับใครก็ตามที่สงสัยว่าทำไมใคร ๆ ก็อยากทำสิ่งนี้มันมีประโยชน์อย่างยิ่งเมื่อเขียนการทดสอบหน่วย / การรวมสำหรับรหัสเดิมที่ใช้วิธีการแบบคงที่
Dezzamondo

เทคนิคนี้ทำงานได้ดีมากสำหรับการใช้ RESTful API ที่รวดเร็วซึ่งจำเป็นในการคงข้อมูล แต่ไม่สามารถใช้ฐานข้อมูลได้ การนำไปใช้งานได้เฉพาะกับวัตถุ C # ในหน่วยความจำดังนั้นจึงไม่มีที่สำหรับจัดเก็บข้อมูล แต่การใช้คุณสมบัติแบบคงที่ช่วยลดความจำเป็นในการใช้ฐานข้อมูลในหน่วยความจำโดยใช้ EF Core หรือ SQLite
gware

19

สมาชิกแบบคงที่ถูกต้องตามกฎหมายใน CLR ไม่ใช่ C #

คุณสามารถใช้กาวบางส่วนใน IL เพื่อเชื่อมโยงรายละเอียดการใช้งาน

ไม่แน่ใจว่าคอมไพเลอร์ C # จะอนุญาตให้เรียกมันได้หรือไม่?

ดู: 8.9.4 นิยามประเภทอินเทอร์เฟซ ECMA-335

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

ในทำนองเดียวกันนิยามประเภทอินเทอร์เฟซจะต้องไม่จัดเตรียมการใช้งานสำหรับวิธีการใด ๆ กับค่าของประเภท อย่างไรก็ตามคำจำกัดความประเภทอินเทอร์เฟซสามารถและโดยปกติจะกำหนดสัญญาวิธีการ (ชื่อวิธีการและลายเซ็นวิธีการ) ที่จะนำไปใช้โดยประเภทการสนับสนุน นิยามประเภทอินเทอร์เฟซสามารถกำหนดและใช้วิธีการแบบคงที่ (ดู see8.4.3) เนื่องจากวิธีการแบบคงที่เชื่อมโยงกับชนิดของอินเทอร์เฟซเองแทนที่จะเป็นค่าใด ๆ ของชนิด


10
สำหรับการอ้างอิงCLS Rule 19: CLS-compliant interfaces shall not define static methods, nor shall they define fields.มีการกล่าวต่อไปว่าผู้บริโภคที่เข้ากันได้กับ CLS จะปฏิเสธอินเทอร์เฟซประเภทนี้ได้เป็นเรื่องปกติ ฉันพยายามเมื่อประมาณหนึ่งปีที่แล้วเพื่อเรียกวิธีการแบบคงที่บนอินเทอร์เฟซและคอมไพเลอร์ C # จะไม่รวบรวมมัน
Christopher Currens

เพิ่มเติมจาก @ChristopherCurrens หมายเหตุเกี่ยวกับ CLS: Common Language Specification (CLS) is a set of basic language features that .Net Languages needed.... When there is a situation to communicate Objects written in different .Net Complaint languages , those objects must expose the features that are common to all the languages. มันสมเหตุสมผลแล้วถ้า CLS เกี่ยวกับการทำงานร่วมกันในภาษา. NET ที่แตกต่างกันและ C # ไม่อนุญาตให้สมาชิกแบบคงที่บนอินเทอร์เฟซ CLS ก็จะห้ามพวกเขาเช่นกันเพื่อให้แน่ใจว่าไลบรารีใน NET อื่น ๆ สามารถเรียกได้จาก C #
Simon Tewsi

17

คุณสามารถกำหนดวิธีการแบบคงที่ใน c # 8 แต่คุณต้องประกาศเนื้อความเริ่มต้นสำหรับมัน

    public interface IMyInterface
    {
          static string GetHello() =>  "Default Hello from interface" ;
          static void WriteWorld() => Console.WriteLine("Writing World from interface");
    }

หรือถ้าคุณไม่ต้องการมีเนื้อหาเริ่มต้นใด ๆ ก็ให้โยนข้อยกเว้น:

    public interface IMyInterface
    {
          static string GetHello() =>  throw new NotImplementedException() ;
          static void WriteWorld() => throw new NotImplementedException();
    }

ดูเหมือนว่าสมาชิกแบบคงที่ในอินเทอร์เฟซจะค่อนข้างไร้ประโยชน์เพราะคุณไม่สามารถเข้าถึงได้จากอินสแตนซ์ของอินเทอร์เฟซ อย่างน้อยใน C # 8
Pavel Sapehin

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

5

คุณสามารถเรียกมันด้วยการสะท้อน:

MyInterface.GetType().InvokeMember("StaticMethod", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, null);

5
และถ้าคุณไม่มีอินสแตนซ์ของ MyInterface คุณสามารถใช้ "typeOf (MyInterface)" แทน "myInterface.GetType ()" ได้
RenniePet

ดูเหมือนเป็นความคิดที่ดีในเวลานั้นและฉันอาจจะทำต่อไปโดยการไตร่ตรอง แต่คำเตือนเล็ก ๆ น้อย ๆ : มันจะมีปัญหามากขึ้นหากโปรแกรมสับสนเช่นนั้น StaticMethod จะเปลี่ยนชื่อ
RenniePet

1
@RenniePet: คุณสามารถจัดการบางส่วนกับการเปลี่ยนชื่อ StaticMethod โดยใช้ nameof (StaticMethod) แทน อาจช่วยได้ด้วยเครื่องทำให้สับสนขึ้นอยู่กับว่ามันเปลี่ยนชื่ออย่างไร หากคุณทำเช่นนี้คุณจะเห็นข้อผิดพลาดเวลาคอมไพล์เป็นอย่างน้อย
Brent Rittenhouse

การสะท้อนกลับรุนแรงเกินไปสำหรับกรณีนี้
Stepan Ivanenko

3

C # "Ten" จะอนุญาตให้สมาชิกคงที่บนอินเทอร์เฟซควบคู่ไปกับบทบาท นับเป็นก้าวที่ยิ่งใหญ่ซึ่งจะช่วยให้ตัวดำเนินการทั่วไปทำงานหนักเกินไปโดยไม่ต้องใช้การสะท้อนใด ๆ นี่คือตัวอย่างตัวอย่างวิธีการทำงานโดยใช้ตัวอย่าง monoid แบบคลาสสิกซึ่งเป็นเพียงศัพท์แสงสำหรับพูดว่า "สิ่งที่สามารถเพิ่มได้" นำมาโดยตรงจากMads Torgersen: C # สู่อนาคต :

interface IMonoid<T>
{
    static T Zero { get; }
    static T operator +(T t1, T t2);
}

public static T AddAll<T>(T[] ts) where T : IMonoid<T>
{
    T result = T.Zero;
    foreach (T t in ts) { result += t; }
    return result;
}

role IntAddMonoid extends int : IMonoid<int>
{
    public static int Zero => 0;
}

IntAddMonoid[] values = new int[] {1, 2, 4, 8, 16, 32};
int sixtyThree = AddAll<IntAddMonoid>(values); // == 63

แหล่งข้อมูลเพิ่มเติม:

Jeremy Bytes: C # 8 อินเทอร์เฟซสมาชิกแบบคงที่

แก้ไข

โพสต์นี้ระบุไว้เดิมสมาชิกคงที่ของอินเทอร์เฟซจะถูกเพิ่มในC # 8.0ซึ่งไม่เป็นความจริงฉันตีความคำพูดของ Mads Torgersen ในวิดีโอผิด คู่มือ C # 8.0 อย่างเป็นทางการยังไม่ได้พูดถึงสมาชิกอินเทอร์เฟซแบบคงที่ แต่เป็นที่ชัดเจนว่าพวกเขาได้ดำเนินการมานานแล้ว


1

สาเหตุที่คุณไม่สามารถใช้วิธีคงที่บนอินเทอร์เฟซได้: เหตุใด C # จึงไม่อนุญาตให้ใช้วิธีการแบบคงที่ในการใช้งานอินเทอร์เฟซ

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

กล่าวคือ

public static class MyStaticClass
{
    public static void MyStaticMethod()
    {...}
}

public interface IStaticWrapper
{
    void MyMethod();
}

public class MyClass : IStaticWrapper
{
    public void MyMethod()
    {
        MyStaticClass.MyStaticMethod();
    }
}

ข้อดีของการใช้อินเทอร์เฟซกับคลาสแบบคงที่มากกว่าการใช้อินเตอร์เฟสเพียงอย่างเดียวคืออะไร?
Selen

1

C # 8 อนุญาตให้สมาชิกคงที่บนอินเทอร์เฟซ

เริ่มต้นด้วย C # 8.0 อินเทอร์เฟซอาจกำหนดการใช้งานเริ่มต้นสำหรับสมาชิก นอกจากนี้ยังอาจกำหนดสมาชิกแบบคงที่เพื่อจัดเตรียมการใช้งานเดียวสำหรับฟังก์ชันการทำงานทั่วไป

อินเทอร์เฟซ (อ้างอิง C #)

เช่น

public interface IGetSomething
{
    public static string Something = "something";
}

var something = IGetSomething.Something;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.