Lớp ẩn danh có thể thực hiện giao diện?


463

Có thể có một loại ẩn danh thực hiện một giao diện?

Tôi đã có một đoạn mã mà tôi muốn làm việc, nhưng không biết làm thế nào để làm điều này.

Tôi đã có một vài câu trả lời là không hoặc tạo một lớp thực hiện giao diện xây dựng các thể hiện mới của điều đó. Điều này không thực sự lý tưởng, nhưng tôi tự hỏi liệu có một cơ chế để tạo ra một lớp động mỏng trên đầu một giao diện sẽ làm cho điều này trở nên đơn giản.

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

Tôi đã tìm thấy một bài viết Gói giao diện động mô tả một cách tiếp cận. Đây có phải là cách tốt nhất để làm điều này?


1
Liên kết xuất hiện lỗi thời, đây có thể là một thay thế phù hợp liensberger.it/web/blog/?p=298 .
Phil Cooper

1
Có, bạn có thể thực hiện việc này với .NET 4 trở lên (thông qua DLR), bằng cách sử dụng gói nuget ImpromptuInterface .
BrainSlugs83

1
@PhilCooper Liên kết của bạn đã ngừng hoạt động, có thể kể từ ít nhất là năm 2016 - nhưng may mắn là nó đã được lưu trữ trước đó. web.archive.org/web/20111105150920/http://www.liensberger.it/ khăn
Paul

Câu trả lời:


361

Không, các loại ẩn danh không thể thực hiện một giao diện. Từ hướng dẫn lập trình C # :

Các loại ẩn danh là các loại lớp bao gồm một hoặc nhiều thuộc tính chỉ đọc công khai. Không có loại thành viên khác như phương thức hoặc sự kiện được cho phép. Một loại ẩn danh không thể được truyền tới bất kỳ giao diện hoặc loại nào ngoại trừ đối tượng.


5
sẽ tốt đẹp để có những thứ này anyway. Nếu bạn đang nói về khả năng đọc mã, các biểu thức lambda thường không phải là hướng đi. Nếu chúng ta đang nói về RAD, tất cả tôi đều thực hiện giao diện ẩn danh giống như java. Nhân tiện, trong một số trường hợp, tính năng đó mạnh hơn các đại biểu
Arsen Zahray

18
@ArsenZahray: biểu thức lambda, khi được sử dụng tốt, thực sự làm tăng khả năng đọc mã. Chúng đặc biệt mạnh khi được sử dụng trong các chuỗi chức năng, có thể làm giảm hoặc loại bỏ nhu cầu về các biến cục bộ.
Roy Tinker

2
Bạn có thể thực hiện thủ thuật theo cách này "Các lớp triển khai ẩn danh - Mẫu thiết kế cho C #" - twistedoakstudios.com/blog/ợi
Dmitry Pavlov

3
@DmitryPavlov, đó là giá trị đáng ngạc nhiên. Người qua đường: đây là phiên bản cô đọng.
kdbanman

1
bạn có thể truyền loại ẩn danh cho một đối tượng ẩn danh với cùng các trường stackoverflow.com/questions/1409734/cast-to-anonymous-type
Zinov

89

Mặc dù đây có thể là một câu hỏi hai năm tuổi và trong khi các câu trả lời trong luồng hoàn toàn đúng, tôi không thể cưỡng lại sự thôi thúc nói với bạn rằng trên thực tế có thể có một lớp ẩn danh thực hiện một giao diện, mặc dù phải mất một một chút gian lận sáng tạo để đạt được điều đó.

Quay trở lại năm 2008, tôi đã viết một nhà cung cấp LINQ tùy chỉnh cho chủ lao động lúc đó của mình và đến một lúc tôi cần có thể nói "các" lớp ẩn danh của mình từ các lớp ẩn danh khác, có nghĩa là họ thực hiện giao diện mà tôi có thể sử dụng để gõ kiểm tra họ Cách chúng tôi giải quyết là bằng cách sử dụng các khía cạnh (chúng tôi đã sử dụng PostSharp ), để thêm việc triển khai giao diện trực tiếp trong IL. Vì vậy, trên thực tế, để các lớp ẩn danh thực hiện các giao diện là có thể thực hiện được , bạn chỉ cần uốn cong các quy tắc một chút để đạt được điều đó.


8
@Gusdor, trong trường hợp này, chúng tôi đã kiểm soát hoàn toàn bản dựng và nó luôn được chạy trên một máy chuyên dụng. Ngoài ra, vì chúng tôi đang sử dụng PostSharp và những gì chúng tôi đang làm là hoàn toàn hợp pháp trong khuôn khổ đó, không có gì có thể thực sự bật lên miễn là chúng tôi đảm bảo PostSharp đã được cài đặt trên máy chủ xây dựng mà chúng tôi đang sử dụng.
Mia Clarke

16
@Gusdor Tôi đồng ý rằng các lập trình viên khác có thể dễ dàng lấy dự án và biên dịch mà không gặp khó khăn lớn, nhưng đó là một vấn đề khác có thể giải quyết một cách riêng biệt, mà không hoàn toàn tránh các công cụ hoặc khung như postsharp. Lập luận tương tự mà bạn đưa ra có thể được đưa ra đối với chính VS hoặc bất kỳ khung MS không chuẩn nào khác không phải là một phần của thông số C #. Bạn cần những thứ đó nếu không nó sẽ "bật". Đó không phải là vấn đề IMO. Vấn đề là khi việc xây dựng trở nên phức tạp đến mức khó có thể khiến những thứ đó hoạt động cùng nhau.
AaronLS

6
@ZainRizvi Không, không. Theo tôi biết, nó vẫn đang được sản xuất. Sự lo lắng nêu lên có vẻ xa lạ với tôi lúc đó, và tùy tiện nhất với tôi bây giờ. Về cơ bản, nó là "Đừng sử dụng một khuôn khổ, mọi thứ sẽ vỡ!". Họ đã không làm gì, không có gì nổi bật, và tôi không ngạc nhiên.
Mia Clarke

3
Bây giờ thì dễ hơn nhiều; không cần sửa đổi IL sau khi mã được tạo; chỉ cần sử dụng ImpromptuInterface . - Nó cho phép bạn liên kết bất kỳ đối tượng nào (bao gồm các đối tượng được nhập ẩn danh) vào bất kỳ giao diện nào (tất nhiên sẽ có ngoại lệ ràng buộc muộn nếu bạn cố gắng sử dụng một phần của giao diện mà lớp không thực sự hỗ trợ).
BrainSlugs83

44

Truyền các loại ẩn danh cho các giao diện là điều tôi muốn trong một thời gian nhưng thật không may, việc triển khai hiện tại buộc bạn phải thực hiện giao diện đó.

Giải pháp tốt nhất xung quanh nó là có một số loại proxy động tạo ra việc triển khai cho bạn. Sử dụng dự án LinFu tuyệt vời, bạn có thể thay thế

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

với

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

17
Dự án Giao diện Impromptu sẽ thực hiện điều này trong .NET 4.0 bằng cách sử dụng DLR và có trọng lượng nhẹ hơn Linfu.
jbtule

DynamicObjectmột loại LinFu? System.Dynamic.DynamicObjectchỉ có một hàm tạo được bảo vệ (ít nhất là trong .NET 4.5).
jdmcnair

Đúng. Tôi đã đề cập đến việc triển khai LinFu DynamicObjecttrước phiên bản DLR
Arne Claassen

15

Các loại ẩn danh có thể thực hiện các giao diện thông qua một proxy động.

Tôi đã viết một phương thức mở rộng trên GitHub và một bài đăng trên blog http://wblo.gs/feE để hỗ trợ cho kịch bản này.

Phương pháp có thể được sử dụng như thế này:

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

Không; một loại ẩn danh không thể được thực hiện để làm bất cứ điều gì ngoại trừ có một vài thuộc tính. Bạn sẽ cần phải tạo loại của riêng bạn. Tôi đã không đọc bài viết được liên kết sâu, nhưng có vẻ như nó sử dụng Reflection.Emit để tạo các loại mới một cách nhanh chóng; nhưng nếu bạn giới hạn thảo luận về những điều trong chính C #, bạn không thể làm những gì bạn muốn.


Và điều quan trọng cần lưu ý: các thuộc tính cũng có thể bao gồm các hàm hoặc khoảng trống (Hành động): chọn new {... MyFunction = new Func <string, bool> (s => value.A == s)} hoạt động mặc dù bạn không thể tham khảo các thuộc tính mới trong các chức năng của bạn (chúng tôi không thể sử dụng "A" thay cho "value.A").
cfeduke

2
Chà, đó không phải là một tài sản tình cờ là một đại biểu sao? Nó không thực sự là một phương pháp.
Marc Gravell

1
Tôi đã sử dụng Reflection.Emit để tạo các loại trong thời gian chạy nhưng tin rằng bây giờ tôi thích giải pháp AOP hơn để tránh chi phí thời gian chạy.
Norman H

11

Giải pháp tốt nhất là không sử dụng các lớp ẩn danh.

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

Lưu ý rằng bạn cần chuyển kết quả của truy vấn sang loại giao diện. Có thể có một cách tốt hơn để làm điều đó, nhưng tôi không thể tìm thấy nó.


2
Bạn có thể sử dụng values.OfType<IDummyInterface>()thay vì đúc. Nó chỉ trả về các đối tượng trong bộ sưu tập của bạn mà thực sự có thể được chuyển sang loại đó. Tất cả phụ thuộc vào những gì bạn muốn.
Kristoffer L

7

Câu trả lời cho câu hỏi cụ thể là không. Nhưng bạn đã nhìn vào khuôn khổ chế giễu? Tôi sử dụng Moq nhưng có hàng triệu người trong số họ ở ngoài đó và họ cho phép bạn triển khai / sơ khai (một phần hoặc toàn bộ) giao diện trực tuyến. Ví dụ.

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

Một tùy chọn khác là tạo một lớp triển khai cụ thể, duy nhất có lambdas trong hàm tạo.

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

Nếu tất cả những gì bạn sẽ làm là chuyển đổi DummySourcethành DummyInterface, thì sẽ đơn giản hơn khi chỉ có một lớp lấy một DummySourcehàm tạo và thực hiện giao diện.

Nhưng, nếu bạn cần chuyển đổi nhiều loại sang DummyInterface, đây là tấm nồi hơi ít hơn nhiều.

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.