คลาสที่ไม่ระบุชื่อสามารถใช้อินเตอร์เฟสได้หรือไม่


463

เป็นไปได้ไหมที่จะมีประเภทที่ไม่ระบุตัวตนใช้งานอินเตอร์เฟส

ฉันมีรหัสชิ้นหนึ่งที่ฉันต้องการใช้งาน แต่ไม่รู้ว่าจะทำอย่างไร

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

public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

ฉันพบบทความการตัดส่วนต่อประสานแบบไดนามิกที่อธิบายวิธีการหนึ่ง นี่เป็นวิธีที่ดีที่สุดในการทำสิ่งนี้หรือไม่?


1
การเชื่อมโยงจะปรากฏออกจากวันนี้อาจจะเป็นทางเลือกที่เหมาะสมliensberger.it/web/blog/?p=298
Phil Cooper

1
ใช่คุณสามารถทำได้ด้วย. NET 4 และสูงกว่า (ผ่าน DLR) โดยใช้แพ็คเกจแพคเกจ nuget ImpromptuInterface
BrainSlugs83

1
@PhilCooper ลิงก์ของคุณอาจใช้งานไม่ได้ตั้งแต่อย่างน้อยปี 2016 แต่โชคดีที่มันถูกเก็บถาวรก่อนหน้านี้ web.archive.org/web/20111105150920/http://www.liensberger.it/…
Paul

คำตอบ:


361

ไม่ประเภทที่ไม่ระบุชื่อไม่สามารถใช้อินเทอร์เฟซได้ จากคู่มือการเขียนโปรแกรม C # :

ประเภทไม่ระบุชื่อเป็นประเภทชั้นเรียนที่ประกอบด้วยคุณสมบัติสาธารณะแบบอ่านอย่างเดียวอย่างน้อยหนึ่งรายการ ไม่อนุญาตให้สมาชิกชั้นเรียนอื่น ๆ เช่นวิธีการหรือเหตุการณ์ได้รับอนุญาต ประเภทที่ไม่ระบุชื่อไม่สามารถส่งไปยังอินเตอร์เฟสหรือประเภทใด ๆ ยกเว้นวัตถุ


5
จะดีที่มีสิ่งนี้ต่อไป หากคุณกำลังพูดถึงการอ่านโค้ดการแสดงออกแลมบ์ดามักไม่ใช่วิธีที่จะไป หากเรากำลังพูดถึง RAD ฉันทุกคนต่างก็มีการใช้อินเทอร์เฟซแบบไม่ระบุชื่อเหมือนจาวา โดยวิธีการในบางกรณีคุณลักษณะที่มีประสิทธิภาพมากกว่าผู้ได้รับมอบหมาย
Arsen Zahray

18
@ArsenZahray: การแสดงออกแลมบ์ดาเมื่อใช้งานได้ดีจะช่วยให้อ่านรหัสได้ง่ายขึ้น พวกมันมีประสิทธิภาพเป็นพิเศษเมื่อใช้ในเชนการทำงานซึ่งสามารถลดหรือขจัดความต้องการตัวแปรท้องถิ่นได้
Roy Tinker

2
คุณสามารถทำสิ่งนี้ได้ด้วยวิธี "การใช้งานแบบไม่เปิดเผยตัวตน - รูปแบบการออกแบบสำหรับ C #" - twistedoakstudios.com/blog/…
Dmitry Pavlov

3
@ DmitryPavlov นั้นมีค่าอย่างน่าประหลาดใจ Passersby: นี่คือเวอร์ชั่นย่อ
kdbanman

1
คุณสามารถส่งประเภทที่ไม่ระบุชื่อไปยังวัตถุที่ไม่ระบุตัวตนด้วยเขตข้อมูลเดียวกันstackoverflow.com/questions/1409734/cast-to-anonymous-type
Zinov

89

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

ย้อนกลับไปในปี 2008 ฉันกำลังเขียนผู้ให้บริการ LINQ ที่กำหนดเองสำหรับนายจ้างของฉันแล้วและ ณ จุดหนึ่งฉันต้องสามารถบอกคลาส "ไม่ระบุชื่อ" ของฉันจากคนที่ไม่ระบุตัวตนอื่น ๆ ได้ซึ่งหมายความว่าพวกเขาใช้อินเทอร์เฟซ พวกเขา วิธีที่เราแก้ไขมันคือการใช้แง่มุมต่างๆ (เราใช้PostSharp ) เพื่อเพิ่มการใช้อินเตอร์เฟสใน IL โดยตรง ดังนั้นในความเป็นจริงการอนุญาตให้คลาสที่ไม่ระบุชื่อใช้อินเทอร์เฟซนั้นเป็นไปได้คุณแค่ต้องก้มกฎเล็กน้อยเพื่อไปที่นั่น


8
@Gusdor ในกรณีนี้เราสามารถควบคุมการสร้างได้อย่างสมบูรณ์และมันก็มักจะทำงานบนเครื่องเฉพาะ นอกจากนี้เนื่องจากเราใช้ PostSharp และสิ่งที่เราทำนั้นถูกกฎหมายอย่างสมบูรณ์ภายในกรอบดังกล่าวจึงไม่มีอะไรสามารถปรากฏได้จริงตราบใดที่เราตรวจสอบให้แน่ใจว่ามีการติดตั้ง PostSharp ไว้ในบิลด์เซิร์ฟเวอร์ที่เราใช้
Mia Clarke

16
@Gusdor ฉันยอมรับว่ามันควรจะง่ายสำหรับโปรแกรมเมอร์คนอื่น ๆ ที่จะได้รับโครงการและรวบรวมโดยไม่ต้องลำบากมาก แต่นั่นเป็นปัญหาที่แตกต่างกัน addressable แยกต่างหากโดยไม่ต้องหลีกเลี่ยงเครื่องมือหรือกรอบเช่น postsharp อาร์กิวเมนต์เดียวกับที่คุณทำสามารถเทียบกับ VS เองหรือเฟรมเวิร์ก MS ที่ไม่ได้มาตรฐานอื่น ๆ ที่ไม่ได้เป็นส่วนหนึ่งของข้อมูลจำเพาะ C # คุณต้องการสิ่งเหล่านั้นมิฉะนั้น "ป๊อปไป" นั่นไม่ใช่ปัญหา IMO ปัญหาคือเมื่อการสร้างมีความซับซ้อนจนยากที่จะทำให้ทุกสิ่งทำงานร่วมกันได้อย่างถูกต้อง
AaronLS

6
@ZainRizvi ไม่มันไม่ได้ เท่าที่ฉันรู้มันยังอยู่ระหว่างการผลิต ความกังวลที่เกิดขึ้นทำให้ฉันรู้สึกแปลกและตอนนี้ฉันก็เลือกที่จะดีที่สุด โดยทั่วไปแล้วจะพูดว่า "อย่าใช้กรอบสิ่งต่าง ๆ จะพัง!" พวกเขาไม่ได้ไม่มีอะไรปรากฏขึ้นและฉันก็ไม่แปลกใจ
Mia Clarke

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

44

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

ทางออกที่ดีที่สุดคือมีพร็อกซีไดนามิกที่สร้างการใช้งานให้คุณ การใช้โครงการ LinFu ที่ยอดเยี่ยมคุณสามารถแทนที่

select new
{
  A = value.A,
  B = value.C + "_" + value.D
};

กับ

 select new DynamicObject(new
 {
   A = value.A,
   B = value.C + "_" + value.D
 }).CreateDuck<DummyInterface>();

17
Impromptu-Interface Projectจะทำสิ่งนี้ใน. NET 4.0 โดยใช้ DLR และมีน้ำหนักเบากว่า Linfu
jbtule

คือDynamicObjectประเภท Linfu? System.Dynamic.DynamicObjectมีตัวสร้างที่ได้รับการป้องกันเท่านั้น (อย่างน้อยใน. NET 4.5)
jdmcnair

ใช่. ฉันหมายถึงการใช้งาน LinFu DynamicObjectซึ่งถือกำเนิดรุ่น DLR
Arne Claassen

15

ประเภทที่ไม่ระบุชื่อสามารถใช้อินเตอร์เฟสผ่านพร็อกซีแบบไดนามิก

ฉันเขียนวิธีการขยายบนGitHubและบล็อกโพสต์http://wblo.gs/feEเพื่อสนับสนุนสถานการณ์นี้

วิธีนี้สามารถใช้ดังนี้:

class Program
{
    static void Main(string[] args)
    {
        var developer = new { Name = "Jason Bowers" };

        PrintDeveloperName(developer.DuckCast<IDeveloper>());

        Console.ReadKey();
    }

    private static void PrintDeveloperName(IDeveloper developer)
    {
        Console.WriteLine(developer.Name);
    }
}

public interface IDeveloper
{
    string Name { get; }
}

13

ไม่มี ประเภทที่ไม่ระบุชื่อไม่สามารถทำอะไรได้ยกเว้นมีคุณสมบัติน้อย คุณจะต้องสร้างประเภทของคุณเอง ฉันไม่ได้อ่านบทความที่เชื่อมโยงในเชิงลึก แต่ดูเหมือนว่ามันจะใช้ Reflection.Emit สร้างประเภทใหม่ได้ทันที แต่ถ้าคุณ จำกัด การอภิปรายสิ่งต่าง ๆภายใน C #คุณไม่สามารถทำสิ่งที่คุณต้องการได้


และสิ่งสำคัญที่ควรทราบ: คุณสมบัติอาจรวมถึงฟังก์ชั่นหรือช่องว่าง (การกระทำ) เช่น: เลือกใหม่ {... MyFunction = ใหม่ Func <string, bool> (s => value.A == s)} ทำงานแม้ว่าคุณจะไม่สามารถอ้างถึง คุณสมบัติใหม่ในฟังก์ชั่นของคุณ (เราไม่สามารถใช้ "A" แทน "value.A")
cfeduke

2
นั่นไม่ใช่แค่คุณสมบัติที่เกิดขึ้นกับตัวแทน มันไม่ใช่วิธีการจริงๆ
Marc Gravell

1
ฉันใช้ Reflection.Emit เพื่อสร้างประเภทที่รันไทม์ แต่เชื่อว่าตอนนี้ฉันต้องการโซลูชัน AOP เพื่อหลีกเลี่ยงค่าใช้จ่ายรันไทม์
นอร์แมน H

11

ทางออกที่ดีที่สุดคือไม่ใช้คลาสที่ไม่ระบุชื่อ

public class Test
{
    class DummyInterfaceImplementor : IDummyInterface
    {
        public string A { get; set; }
        public string B { get; set; }
    }

    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new DummyInterfaceImplementor()
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values.Cast<IDummyInterface>());

    }

    public void DoSomethingWithDummyInterface(IEnumerable<IDummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

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


2
คุณสามารถใช้values.OfType<IDummyInterface>()แทนการส่ง มันจะส่งคืนวัตถุในคอลเลกชันของคุณเท่านั้นที่สามารถส่งไปเป็นประเภทนั้นได้ ทุกอย่างขึ้นอยู่กับสิ่งที่คุณต้องการ
Kristoffer L

7

คำตอบของคำถามที่ถามโดยเฉพาะคือไม่ แต่คุณได้ดูที่กรอบการเยาะเย้ยหรือไม่? ฉันใช้ขั้นต่ำ แต่มีอยู่นับล้านที่อยู่ข้างนอกและพวกเขาอนุญาตให้คุณใช้งาน / อินเทอร์เฟซอินไลน์บางส่วนหรือทั้งหมด เช่น.

public void ThisWillWork()
{
    var source = new DummySource[0];
    var mock = new Mock<DummyInterface>();

    mock.SetupProperty(m => m.A, source.Select(s => s.A));
    mock.SetupProperty(m => m.B, source.Select(s => s.C + "_" + s.D));

    DoSomethingWithDummyInterface(mock.Object);
}

1

อีกทางเลือกหนึ่งคือการสร้างคลาสการใช้งานที่เป็นรูปธรรมและเป็นรูปธรรมซึ่งใช้ lambdas ในตัวสร้าง

public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

// "Generic" implementing class
public class Dummy : DummyInterface
{
    private readonly Func<string> _getA;
    private readonly Func<string> _getB;

    public Dummy(Func<string> getA, Func<string> getB)
    {
        _getA = getA;
        _getB = getB;
    }

    public string A => _getA();

    public string B => _getB();
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new Dummy // Syntax changes slightly
                     (
                         getA: () => value.A,
                         getB: () => value.C + "_" + value.D
                     );

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

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

แต่ถ้าคุณต้องการแปลงหลายประเภทDummyInterfaceเป็นจานหม้อไอน้ำที่น้อยกว่ามาก

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