Может ли анонимный класс реализовать интерфейс?

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 .
Фил Купер
1
Да, вы можете сделать это с .NET 4 и выше (через DLR), используя пакет nuget ImpromptuInterface .
BrainSlugs83
1
@PhilCooper Ваша ссылка не работает, вероятно, по крайней мере с 2016 года, но, к счастью, она была заархивирована до этого. web.archive.org/web/20111105150920/http://www.liensberger.it/…
Пол

Ответы:

361

Нет, анонимные типы не могут реализовать интерфейс. Из руководства по программированию на C # :

Анонимные типы - это типы классов, состоящие из одного или нескольких общедоступных свойств, доступных только для чтения. Другие типы членов класса, такие как методы или события, не допускаются. Анонимный тип не может быть приведен к какому-либо интерфейсу или типу, кроме объекта.

HasaniH
источник
5
было бы неплохо иметь этот материал в любом случае. Если вы говорите о читабельности кода, лямбда-выражения обычно не подходят. Если мы говорим о RAD, я все в java-подобной реализации анонимного интерфейса. Кстати, в некоторых случаях эта функция более мощная, чем у делегатов
Арсен Захрай
18
@ArsenZahray: лямбда-выражения при правильном использовании действительно повышают читабельность кода. Они особенно эффективны при использовании в функциональных цепочках, которые могут уменьшить или устранить необходимость в локальных переменных.
Рой Тинкер
2
Вы могли бы добиться цели следующим образом: «Анонимные классы реализации - шаблон проектирования для C #» - twistedoakstudios.com/blog/…
Дмитрий Павлов
3
@DmitryPavlov, это было удивительно ценно. Прохожие: вот сокращенная версия.
kdbanman
1
Вы можете привести анонимный тип к анонимному объекту с теми же полями stackoverflow.com/questions/1409734/cast-to-anonymous-type
Зинов
90

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

Еще в 2008 году я писал собственного поставщика LINQ для своего тогдашнего работодателя, и в какой-то момент мне нужно было иметь возможность отличать «мои» анонимные классы от других анонимных, что означало, что они реализуют интерфейс, который я мог бы использовать для проверки типов их. Мы решили это с помощью аспектов (мы использовали PostSharp ), чтобы добавить реализацию интерфейса непосредственно в IL. Таким образом, на самом деле, позволяя анонимным классам реализовывать интерфейсы, выполнимо , вам просто нужно немного изменить правила, чтобы попасть туда.

Миа Кларк
источник
8
@Gusdor, в этом случае мы полностью контролировали сборку, и она всегда работала на выделенной машине. Кроме того, поскольку мы использовали PostSharp, и то, что мы делали, полностью легально в этой среде, ничего не получится, если мы убедимся, что PostSharp установлен на сервере сборки, который мы использовали.
Миа Кларк
16
@Gusdor Я согласен, что другим программистам должно быть легко получить проект и скомпилировать его без особых сложностей, но это отдельная проблема, которую можно решить отдельно, без полного отказа от инструментов или сред, таких как postsharp. Тот же аргумент, который вы приводите, может быть сделан против самой VS или любой другой нестандартной среды MS, которая не является частью спецификации C #. Вы нуждаетесь в этих вещах, иначе это «всплывает». Это не проблема ИМО. Проблема в том, что сборка становится настолько сложной, что трудно заставить все эти вещи работать вместе правильно.
AaronLS
6
@ZainRizvi Нет, это не так. Насколько я знаю, он все еще в производстве. Поднятая обеспокоенность казалась мне странной, а в лучшем случае - произвольной. В основном говорилось: «Не используйте рамки, все сломается!». Нет, ничего не лопнуло, и я не удивлен.
Миа Кларк
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тип Линьфа? System.Dynamic.DynamicObjectимеет только защищенный конструктор (по крайней мере, в .NET 4.5).
jdmcnair
Да. Я имел в виду реализацию LinFu, DynamicObjectкоторая предшествовала версии DLR
Арне Клаассен,
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 #, вы не можете делать то, что хотите.

Марк Гравелл
источник
И важно отметить: свойства могут также включать функции или пустоты (Action): выберите new {... MyFunction = new 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);
        }
    }
}

Обратите внимание, что вам нужно привести результат запроса к типу интерфейса. Возможно, есть лучший способ сделать это, но я не смог его найти.

ICR
источник
2
Вы можете использовать values.OfType<IDummyInterface>()вместо приведения. Он возвращает только те объекты в вашей коллекции, которые на самом деле можно привести к этому типу. Все зависит от того, что вы хотите.
Кристоффер Л
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);
}
Девять хвостов
источник
1

Другой вариант - создать один конкретный реализующий класс, который принимает лямбда-выражения в конструкторе.

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, это гораздо меньше, чем котельная плита.

Гордон Бин
источник