Чи може анонімний клас реалізувати інтерфейс?


462

Чи можливо мати анонімний тип реалізації інтерфейсу?

У мене є фрагмент коду, над яким я хотів би працювати, але не знаю, як це зробити.

У мене було кілька відповідей, які або кажуть «ні», або створюють клас, який реалізує інтерфейсну конструкцію нових примірників цього. Це насправді не ідеально, але мені цікаво, чи існує механізм створення тонкого динамічного класу поверх інтерфейсу, який би спростив це.

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 .
Філ Купер

1
Так, ви можете зробити це за допомогою .NET 4 і новіших версій (через DLR), використовуючи нульовий пакет ImpromptuInterface .
BrainSlugs83

1
@PhilCooper Ваше посилання було закрито, ймовірно, принаймні з 2016 року, але, на щастя, воно було заархівоване раніше. web.archive.org/web/20111105150920/http://www.liensberger.it/…
Павло

Відповіді:


360

Ні, анонімні типи не можуть реалізувати інтерфейс. З посібника з програмування C # :

Анонімні типи - це типи класів, які складаються з одного або кількох загальнодоступних властивостей лише для читання Не допускаються інші види членів класу, такі як методи або події. Анонімний тип не може бути переданий до будь-якого інтерфейсу чи типу, крім об'єкта.


5
було б непогано мати цей матеріал все одно. Якщо ви говорите про читабельність коду, лямбда-вирази, як правило, не є способом. Якщо ми говоримо про RAD, я все вживаю в анонімний інтерфейс, схожий на Java. До речі, в деяких випадках ця риса є більш сильною, ніж делегати
Арсен Захрай

18
@ArsenZahray: лямбда-вирази, коли добре використовуються, фактично підвищують читабельність коду. Вони особливо потужні при використанні у функціональних ланцюгах, що може зменшити або усунути потребу в локальних змінних.
Рой Тінкер

2
Ви можете зробити трюк таким чином "Класи анонімного впровадження - модель дизайну для C #" - twistedoakstudios.com/blog/…
Дмитро Павлов

3
@DmitryPavlov, це було напрочуд цінним. Перехожі: ось стислий варіант.
kdbanman

1
ви можете передати анонімний тип анонімному об’єкту з тими ж полями stackoverflow.com/questions/1409734/cast-to-anonymous-type
Зінов,

89

Хоча це може бути два роки питання, і в той час як відповіді в темі все вірно досить, я не можу опиратися спокусі сказати вам , що це насправді можна мати анонімний клас реалізувати інтерфейс, навіть якщо це займає трохи творчого обману, щоб потрапити туди.

Ще в 2008 році я писав спеціальний постачальник LINQ для свого тодішнього роботодавця, і в один момент мені потрібно було вміти розповідати про "мої" анонімні класи від інших анонімних, що означало, щоб вони реалізували інтерфейс, який я міг би використовувати для перевірки їх. Ми вирішили це шляхом використання аспектів (ми використовували PostSharp ), щоб додати реалізацію інтерфейсу безпосередньо в ІЛ. Тож насправді дозволяти анонімним класам реалізовувати інтерфейси доцільно , вам просто потрібно трохи зігнути правила, щоб потрапити туди.


8
@Gusdor, в цьому випадку ми мали повний контроль над збіркою, і він завжди працював на спеціальній машині. Крім того, оскільки ми використовували PostSharp, і те, що ми робимо, є цілком законним у цій рамках, нічого насправді не може з’явитися, поки ми переконалися, що PostSharp встановлений на сервері збірки, який ми використовували.
Міа Кларк

16
@Gusdor Я погоджуюся, що іншим програмістам має бути легко отримати проект і компілювати без великих труднощів, але це інший випуск, який можна вирішувати окремо, без повного уникнення інструментальних засобів або рамок, таких як postsharp. Той же аргумент, який ви робите, може бути зроблений проти самого VS або будь-якого іншого нестандартного фреймворку MS, який не є частиною специфікації C #. Вам потрібні ті речі, інакше це "йде на поп". Це не проблема ІМО. Проблема полягає в тому, коли збірка стає настільки складною, що важко змусити ці речі добре працювати разом.
AaronLS

6
@ZainRizvi Ні, це не так. Наскільки я знаю, це все ще у виробництві. Викликане занепокоєння мені тоді здавалося дивним, а в кращому випадку для мене зараз. В основному було сказано "Не використовуйте рамки, все зламається!". Вони не зробили нічого, і я не здивований.
Міа Кларк

3
Зараз набагато простіше; не потрібно змінювати ІЛ після генерування коду; просто використовуйте 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
Арн Клаассен,

14

Анонімні типи можуть реалізовувати інтерфейси через динамічний проксі.

Я написав метод розширення на 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
Ну хіба це не лише властивість, яка, можливо, є делегатом? Це насправді не метод.
Марк Гравелл

1
Я використовував Reflection.Emit для створення типів під час виконання, але повірте, я вважаю за краще рішення AOP, щоб уникнути витрат на виконання.
Норман Н

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>()замість ролі. Він повертає лише ті об’єкти у вашій колекції, які насправді можуть бути передані до цього типу. Все залежить від того, що ви хочете.
Крістоффер L

7

Відповідь на спеціально задане питання - ні. Але ви дивилися на глузуючі рамки? Я використовую MOQ, але їх є мільйони, і вони дозволяють реалізовувати / заглушувати (частково або повністю) інтерфейси в рядку. Напр.

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

0

Інший варіант - створити єдиний, конкретний клас реалізації, який займає лямбда в конструкторі.

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, це набагато менше плит котла.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.