Czy anonimowa klasa może zaimplementować interfejs?


462

Czy możliwe jest, aby anonimowy typ zaimplementował interfejs?

Mam kawałek kodu, który chciałbym pracować, ale nie wiem, jak to zrobić.

Mam kilka odpowiedzi, które albo mówią „nie”, albo tworzę klasę, która implementuje interfejs, konstruuje nowe instancje tego. To nie jest naprawdę idealne, ale zastanawiam się, czy istnieje mechanizm do tworzenia cienkiej dynamicznej klasy na interfejsie, który by tak uprościł.

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

Znalazłem artykuł Zawijanie interfejsu dynamicznego, który opisuje jedno podejście. Czy to najlepszy sposób na zrobienie tego?


1
Link jest nieaktualny, może to być odpowiednia alternatywa dla liensberger.it/web/blog/?p=298 .
Phil Cooper

1
Tak, możesz to zrobić z .NET 4 i nowszymi (przez DLR), używając pakietu nuget ImpromptuInterface .
BrainSlugs83

1
@PhilCooper Twój link nie działa, prawdopodobnie od co najmniej 2016 r. - ale na szczęście został zarchiwizowany wcześniej. web.archive.org/web/20111105150920/http://www.liensberger.it/…
Paul

Odpowiedzi:


360

Nie, typy anonimowe nie mogą implementować interfejsu. Z przewodnika po programowaniu w C # :

Anonimowe typy to typy klas, które składają się z jednej lub więcej publicznych właściwości tylko do odczytu. Żadne inne rodzaje członków klasy, takie jak metody lub zdarzenia, są niedozwolone. Anonimowego typu nie można rzutować na żaden interfejs lub typ z wyjątkiem obiektu.


5
i tak byłoby miło mieć takie rzeczy. Jeśli mówisz o czytelności kodu, wyrażenia lambda zwykle nie są dobrym rozwiązaniem. Jeśli mówimy o RAD, to jestem za implementacją anonimowego interfejsu java. Nawiasem mówiąc, w niektórych przypadkach ta funkcja jest silniejsza niż delegaci
Arsen Zahray

18
@ArsenZahray: dobrze wyrażone wyrażenia lambda faktycznie zwiększają czytelność kodu. Są szczególnie wydajne, gdy są używane w łańcuchach funkcjonalnych, które mogą zmniejszyć lub wyeliminować potrzebę zmiennych lokalnych.
Roy Tinker

2
Możesz zrobić to w ten sposób: „Anonimowe klasy implementacyjne - wzorzec projektowy dla C #” - twistedoakstudios.com/blog/…
Dmitry Pavlov

3
@DmitryPavlov, to było zaskakująco cenne. Passersby: oto wersja skrócona.
kdbanman

1
możesz rzucić anonimowy typ na anonimowy obiekt za pomocą tych samych pól stackoverflow.com/questions/1409734/cast-to-anonymous-type
Zinov

89

Chociaż może to być pytanie dwuletnie i chociaż wszystkie odpowiedzi w wątku są wystarczająco prawdziwe, nie mogę się oprzeć pokusie, aby powiedzieć, że w rzeczywistości możliwe jest, aby anonimowa klasa zaimplementowała interfejs, nawet jeśli wymaga to trochę kreatywnego oszukiwania, aby się tam dostać.

W 2008 roku pisałem niestandardowego dostawcę LINQ dla mojego ówczesnego pracodawcy, i w pewnym momencie musiałem móc odróżnić „moje” anonimowe klasy od innych anonimowych, co oznaczało, że musieliby oni zaimplementować interfejs, którego mógłbym użyć do sprawdzania typu im. Sposób, w jaki to rozwiązaliśmy, polegał na użyciu aspektów (użyliśmy PostSharp ), aby dodać implementację interfejsu bezpośrednio w IL. W rzeczywistości pozwolenie anonimowym klasom na implementację interfejsów jest wykonalne , wystarczy lekko zgiąć reguły, aby się tam dostać.


8
@Gusdor, w tym przypadku, mieliśmy pełną kontrolę nad kompilacją i zawsze była uruchamiana na dedykowanym komputerze. Ponadto, ponieważ korzystaliśmy z PostSharp, a to, co robiliśmy, jest w pełni legalne w tych ramach, nic tak naprawdę nie może pójść pop, dopóki upewnimy się, że PostSharp jest zainstalowany na serwerze kompilacji, którego używamy.
Mia Clarke,

16
@Gusdor Zgadzam się, że inni programiści powinni mieć łatwy dostęp do projektu i kompilacji bez większych trudności, ale jest to inny problem, który można rozwiązać osobno, bez całkowitego unikania narzędzi lub ram, takich jak postsharp. Ten sam argument, który wysyłasz, można wysunąć przeciwko samemu VS lub dowolnemu innemu niestandardowemu frameworkowi MS, który nie jest częścią specyfikacji C #. Potrzebujesz tych rzeczy, bo inaczej „pop”. To nie jest problem IMO. Problem polega na tym, że kompilacja staje się tak skomplikowana, że ​​trudno jest wszystko dobrze połączyć.
AaronLS,

6
@ZainRizvi Nie, nie zrobił. O ile mi wiadomo, wciąż jest w produkcji. Zgłoszone obawy wydawały mi się wtedy dziwne, a teraz w najlepszym razie arbitralne. Mówiło się po prostu: „Nie używaj frameworka, wszystko się zepsuje!”. Nie zrobili tego, nic nie wyszło, i nie jestem zaskoczony.
Mia Clarke,

3
Teraz jest o wiele łatwiej; nie ma potrzeby modyfikowania IL po wygenerowaniu kodu; wystarczy użyć ImpromptuInterface . - Pozwala powiązać dowolny obiekt (w tym obiekty o typie anonimowym) z dowolnym interfejsem (oczywiście będą występować wyjątki związane z późnym wiązaniem, jeśli spróbujesz użyć części interfejsu, której klasa w rzeczywistości nie obsługuje).
BrainSlugs83

44

Rzucanie anonimowych typów na interfejsy było czymś, czego chciałem od dłuższego czasu, ale niestety obecna implementacja zmusza cię do implementacji tego interfejsu.

Najlepszym rozwiązaniem jest posiadanie pewnego rodzaju dynamicznego serwera proxy, który tworzy dla Ciebie implementację. Korzystając z doskonałego projektu LinFu, możesz go wymienić

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

z

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

17
Impromptu-Interface Project zrobi to w .NET 4.0 za pomocą DLR i jest lżejszy niż Linfu.
jbtule

Czy jest DynamicObjectto typ LinFu? System.Dynamic.DynamicObjectma tylko chroniony konstruktor (przynajmniej w .NET 4.5).
jdmcnair

Tak. Miałem na myśli implementację LinFu, DynamicObjectktórej wersja poprzedza wersję DLR
Arne Claassen

14

Anonimowe typy mogą implementować interfejsy za pośrednictwem dynamicznego serwera proxy.

Napisałem metodę rozszerzenia na GitHub i post na blogu http://wblo.gs/feE, aby wesprzeć ten scenariusz.

Metodę można zastosować w następujący sposób:

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

Nie; typu anonimowego nie można zmusić do robienia czegokolwiek poza kilkoma właściwościami. Musisz stworzyć własny typ. Nie przeczytałem dogłębnie tego artykułu, ale wygląda na to, że używa Reflection.Emit do tworzenia nowych typów w locie; ale jeśli ograniczysz dyskusję do rzeczy w samym C #, nie możesz robić tego, co chcesz.


I ważne, aby pamiętać: właściwości mogą również zawierać funkcje lub puste przestrzenie (Action): wybierz nowy {... MyFunction = new Func <string, bool> (s => value.A == s)} działa, chociaż nie możesz się do niego odwoływać nowe właściwości w twoich funkcjach (nie możemy użyć „A” zamiast „value.A”).
cfeduke

2
Czy to nie tylko własność, która przypadkiem jest delegatem? To właściwie nie jest metoda.
Marc Gravell

1
Użyłem Reflection.Emit do tworzenia typów w środowisku wykonawczym, ale teraz myślę, że wolałbym rozwiązanie AOP, aby uniknąć kosztów środowiska wykonawczego.
Norman H

11

Najlepszym rozwiązaniem jest po prostu nie używanie anonimowych klas.

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

Pamiętaj, że musisz rzutować wynik zapytania na typ interfejsu. Może być lepszy sposób, aby to zrobić, ale nie mogłem go znaleźć.


2
Możesz użyć values.OfType<IDummyInterface>()zamiast obsady. Zwraca tylko obiekty w Twojej kolekcji, które faktycznie można rzutować na ten typ. Wszystko zależy od tego, czego chcesz.
Kristoffer L,

7

Odpowiedź na zadane pytanie brzmi „nie”. Ale czy patrzyłeś na szydzące frameworki? Używam MOQ, ale są tam miliony i pozwalają one na implementację / stub (częściowo lub całkowicie) interfejsów w linii. Na przykład.

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

Inną opcją jest utworzenie pojedynczej, konkretnej klasy implementacyjnej, która przyjmuje lambdas w konstruktorze.

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

Jeśli wszystko, co kiedykolwiek zamiar zrobić, to konwersja DummySourcedo DummyInterface, wtedy byłoby prościej po prostu jedną klasę, która trwa DummySourcew konstruktorze i implementuje interfejs.

Ale jeśli musisz przekonwertować wiele typów DummyInterface, jest to znacznie mniej płyty kotła.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.