Skip to: Site menu | Main content

Autor

Chłopak z Bałut (Dołów), po uniwerku i stypendium. Wiecznie zestresowane, przemądrzałe bezguście. Więcej na stronie domowej.

Jawne implementowanie interfejsów

Czepianie, Klepanie

Znowu mam powód do ponarzekania. I znowu dzięki panom dizajnerom z Microsoftu, którzy zrobili wszystko, żeby utrudnić życie programistom. A wszystko za sprawą cudownego mechanizmu zwanego explicit implementation. Generalnie rzecz biorąc idea może nie jest taka najgłupsza, ale dlaczego ja się pytam, tak dużo uwagi przyłożono do tego, aby utrudnić ludziom życie?

Historia zaczyna się tak, że tworzymy sobie pakiet rozszerzający VisualStudio. Robi się to w ten miły sposób, że dziedziczymy z odpowiedniej abstrakcyjnej klasy Package, dekorujemy ją jakąś setką atrybutów (z czego połowa to GUIDy, jedna czwarta to numery kluczy do resource'ów, a reszta to jakieś inne stringi) i implementujemy sobie jakieś tam metody. Do tego przychodzi nam na myśl, że fajnie byłoby zaimplementować interfejs IOleCośTam, który już jest implementowany przez klasę Package. Jest to o tyle fajna sytuacja, że w większości przypadków moglibyśmy się zdać na Package i obsługiwać tylko te, które nas interesują. I tu w naszą drogę wchodzi właśnie jawna implementacja interfejsów. Dla uproszczenia przyjmijmy taką sytuację:

    public interface IFoo
    {
        void Foo();
    }
    public class Bar : IFoo
    {
        void IFoo.Foo() { ... }
    }

Tak więc mamy sobie fajną klasę Bar. Wydziedziczymy z niej sobie naszą nową klasę, w której spróbujemy przeciążyć metodę Foo, ale w taki sposób, żeby wywołać Foo z klasy Bar.

    public class Baaz : Bar, IFoo
    {
        public Foo()
        {
            base.Foo();
        }
    }

Bad luck! Metoda Foo nie została w klasie Bar bez powodu oznaczona jako publiczna, żebyśmy ją sobie teraz ot tak po prostu mogli wywołać.... no dobra, to może tak:

    public class Baaz : Bar, IFoo
    {
        public Foo()
        {
            ((IFoo)base).Foo();
        }
    }

I to jest właśnie to miejsce, w którym panowie z Microsoftu na chama stawiają szlaban. Otóż kompilator mówi, że wksaźnika base nie można sobie przerzutować na coś innego. Oczywiście przerzutowanie this na odpowiedni interfejs kompiluje się poprawnie i w sposób jak najbardziej poprawny prowadzi do stack overflow poprzez nieskończoną rekurencję. Więc jak? Otóż tak:

    public class Baaz : Bar, IFoo
    {
        public Foo()
        {
            Type t = typeof(Bar);
            InterfaceMapping map = t.GetInterfaceMap(typeof(IFoo));
            MethodInfo mi = Array.Find(map.TargetMethods, 
                    delegate(MethodInfo m) { return m.Name.Equals("Bar.Foo"); });
            
            mi.Invoke(this, null);
        }
    }

Prawda, że elegancko? Dobieramy się do metody klasy bazowej przez reflection (trzeba tutaj zwrócić uwagę na podanie nazwy metody poprzedzonej kwalifikowaną nazwą klasy), po czym wywołujemy ją przy pomocy Invoke. Oczywiście możemy zapomnieć o kontroli typów.

I ja się teraz pytam. Skoro do tej metody i tak można się dostać, skoro istnieją takie sytuacje, w których to jest konieczne, to dlaczego, dlaczego, dlaczego musiało to zostać tak cholernie utrudnione? Dlaczego nie można było dać ludziom do ręki normalnego mechanizmu dostępu do tych metod?

Panów dizajnerów C# radzę wysłać na tygodniowy kurs Pythona. Może przy następnej próbie utrudniania życia programistom ręka im zadrży.

29 stycznia 2007, 21:20:22

Komentarze

skolima, 29 stycznia 2007, 22:18:20

Kompilator próbuje w tym przypadku (dość niefortunnie i pokrętnie) wymusić pozostałą część kontraktu związaną z implementacja explicite interfejsów. Otóż wytyczne z nimi związane zalecają, by takie metody delegowały swoje wykonanie do implementacji implicite, zazwyczaj doprecyzowując przy tym typy parametrów lub wartości zwracanej.
Jeśli trzymać się tego, to odwołanie do implementacji explicite nie jest do niczego potrzebne w klasie potomnej – oznaczałoby tylko wykonanie dodatkowych konwersji typów. A jeśli się do tego nie stosuje, to należy się liczyć ze szczerą nienawiścią późniejszych użytkowników kodu ;-)

Treść komentarza można formatować zgodnie ze składnią Markdown.
Odpowiedzi śledzić można przy pomocy RSS RSS .