2010-04-18 13 views
31

W języku C# modyfikator new może być użyty do ukrycia metody klasy bazowej bez przesłonięcia metody klasy bazowej.Metoda jest zawsze dobrym pomysłem ukrywanie

Nigdy nie spotkałem się z sytuacją, w której ukrycie metody było najlepszym wyborem. Czy są sytuacje, w których ukrywanie metod jest najlepszym wyborem?

+0

Współzmienność typu powrotnego jest wyraźnie powszechnym przypadkiem użycia. W rzeczywistości spotkałem się z tą sytuacją więcej niż raz i uważałem, że nie da się tego zrobić. Teraz wiem lepiej.Ukrywanie własności w kontrolkach to niezła sztuczka. Przykład GST Erica Lipperta ma sens logiczny, ale odbiega tak daleko od typowych idiomów OOP, że byłbym bardzo ostrożny w jego używaniu. – ScottS

Odpowiedz

18

Istnieją rzadkie, ale bardzo dobre powody do ukrywania metod. Eric Lippert posted a great example na swoim blogu:

interface IEnumerable<T> : IEnumerable { 
    new IEnumerator<T> GetEnumerator(); 
} 

Myślę jednak, że ukrywanie powinno być wyjątkiem, a jedynie stosowane z umiarem.

+2

Tak, ale Eric Lippert komentuje później: "Oczywiście, jeśli C# miałby metodę zwrotu typu kowariancji, nie musiałaby to być nowa metoda, co daje nam większy powód ukrywania metody, pozwala na coś w rodzaju typu zwracanego. kowariancja w języku, który nie ma takiej cechy. " . Pytanie brzmi: dlaczego C# nie obsługuje kowariancji typu powrotu? – ceztko

+0

Aha, zapomniałem: faktycznie mam przykład użycia "nowego", który moim zdaniem jest lepszy od GetEnumerator(). – ceztko

4

Tak, jeśli na przykład opublikowano tylko konsumenta klasy bazowej i nowej wersji klasy bazowej, to nagle metoda ma dokładnie taki sam podpis, jak jedna z metod, które już zostały zaimplementowane w klasie pochodnej, musisz ukryć metodę klasy podstawowej i użyć new, aby wyjaśnić, że ukrywasz metodę klasy podstawowej ...

+1

Jest to jedyne uzasadnione użycie, o którym wiedziałem, gdy zadałem to pytanie. Bardzo się starałbym zmienić nazwy metod i uniknąć potrzeby nowych. Gdybym nie mógł zmienić nazwy, prawdopodobnie wprowadziłbym metodę z nową nazwą i oznaczyłabym metodę konfliktu jako przestarzałą w tym samym czasie, co dodanie nowego do ukrycia metody bazowej. – ScottS

+1

Jeszcze gorsze niż możliwość dodania klasy bazowej metodą o tej samej nazwie i sygnaturze byłaby możliwość dodania przez klasę podstawową identycznie nazwanej metody z * innym * podpisem. Jeśli 'DerivedFoo' ma metodę' DoSomething (double) ', a przyszła wersja' Foo' dodaje metodę 'DoSomething (int)', kod może oczekiwać 'derivedThing.DoSomething (4)' do wywołania metody klasy pochodnej , ale nowe przeciążenie w klasie bazowej można uznać za "lepsze dopasowanie". Nie wszystkie odmiany tego problemu spowodują diagnostykę kompilatora. – supercat

14

Często jest to dobry wybór podczas tworzenia niestandardowe elementy sterujące i chcesz zapobiec wyświetlaniu niektórych właściwości w projektancie. Czasami właściwości nie są zastępowalne, ale projektant nie dba o to, dba tylko o to, czy własność publiczna na najniższym poziomie ma atrybut [Browsable].

Załóżmy na przykład, że kontrola nie obsługuje dopełnienia. Właściwość Control.Padding nie jest możliwa do zastąpienia. Ale wiesz też, że nic złego nie stanie się, jeśli ktoś ustawi dopełnienie, po prostu właściwość nie będzie zawierała niczego, więc nie chcesz, aby Twoi użytkownicy widzieli to w projektancie i myślę, że to faktycznie działa. Więc ją ukryć:

public class MyControl : Control 
{ 
    [Browsable(false)] 
    public new Padding Padding 
    { 
     get { return base.Padding; } 
     set { base.Padding = value; } 
    } 
} 

w tym przypadku jesteśmy dosłownie za pomocą elementu ukrywa się ukryć element - od projektanta.

Odkładając na bok, zdaję sobie sprawę, że istnieją inne sposoby osiągnięcia tego celu - jest to tylko jedna z możliwych opcji.

+0

Używam tej metody, aby ukryć właściwość. Jakie są inne środki, o których mówisz? masz link? –

+0

@Pierre: Możesz także zaimplementować własny 'TypeDescriptor' lub' TypeConverter'. Słabością (lub siłą, w zależności od wymagań) tej metody jest to, że jest ona włączona, podczas gdy wersja ukrywająca członka jest opcją rezygnacji. – Aaronaught

15

staram się go używać czasami dla wygody dla dzwoniących, coś jak:

abstract public class Animal { } 

public class Mouse : Animal { } 



public class AnimalTrap 
{ 
    public Animal TrappedAnimal { get; } 
} 

public class MouseTrap : AnimalTrap 
{ 
    new public Mouse TrappedAnimal 
    { 
     get { return (Mouse)base.TrappedAnimal; } 
    } 
} 

Więc rozmówca nie musi rzutować na Mouse sami, gdy klasa pochodna gwarantuje uwięzione zwierzę będzie zawsze mysz . Funkcjonalność polimorficzna pozostaje nienaruszona.

+2

+1 To jest ładny, jasny przykład kowariancji typu powrotu, o której mówi blog Eric Lippert. Musiałem zatrzymać się i pomyśleć, aby pojąć przykład IEnumerable , ale jest to proste. – ScottS

+1

@ScottS: Jest to dobry przykład ukrywania metody, ale nie ma nic wspólnego z kowariancją typu powrotu. W rzeczywistości nie ma tu gwarancji bezpieczeństwa typu, dzwoniący może rzucić 'MouseTrap' z powrotem na' AnimalTrap' i umieścić tam inny typ 'Animal'. – Aaronaught

+0

@Aaronaught, moje rozumienie kowariancji typu powrotu po prostu zmienia typ zwracanej metody w klasie pochodnej. Czy czegoś brakuje? Ta emulacja koinwersacji typu powrotu wymaga, aby nowy typ zwracania mógł być rzutowany, aby był zgodny z typem (ami) powrotu rodzica (ów). Przypuszczalnie MouseTrap ma pewną logikę, która pozwala mu tylko pułapkować myszy. Następnie możesz wywołać funkcję AnimalTrap.TrappedAnimal na instancji MouseTrap i bezpiecznie pobrać mysz. – ScottS

6

Najpopularniejszym przykładem, jaki mogę tu wymyślić, są rzeczy takie jak DbCommand vs SqlCommand; typy betonu (SqlCommand itp.) zazwyczaj zawierają wiele metod ukrywania, aby typy zwracane właściwości/metod wyświetlały prawidłowy typ implementacji. Dzieje się tak, ponieważ powiązane obiekty same mają dodatkowe (specyficzne dla implementacji) funkcje, a wywołujący nie chce rzutować za każdym razem, gdy wywołają cokolwiek.

1

Musiałem uciekać się do metody ukrywania się raz. Używałem biblioteki innej firmy, która zapewniała metodę, która nie była wirtualna (to klucz do wszystkiego). Była to metoda, która przydzieliła instancję czegoś. Ale potrzebowałem rozszerzyć funkcjonalność tej metody.

Użyłem słowa kluczowego "new", aby ukryć implementację klasy podstawowej i udostępnić własne w klasie pochodnej. Nie oznacza to, że nie nazwałem metody klasy bazowej. Właściwie to nazwałem metodę klasy bazowej, ale zrobiłem też inne rzeczy w mojej metodzie i wszystko w końcu wyszło dobrze.

Więc powiedziałbym powody byłyby następujące:

  1. klasa A podstawa znajduje się w 3rd biblioteki partii, że nie masz kontroli nad.
  2. Klasa podstawowa ma metodę inną niż wirtualna, którą należy przesłonić.
  3. Zawsze zwracaj szczególną uwagę na rozszerzenie funkcjonalności, która nie zastępuje go.
  4. I zawsze korzystać z tego w ostateczności, gdy zatrzymany .... :)

Moje 2 centów ...

0

Raz już ją się w kontroli niestandardowych WinForm. Moja niestandardowa kontrola zastępuje właściwość Text i chcę podnieść zdarzenie za każdym razem, gdy ulegnie zmianie. Klasa podstawowa Control jest dostarczana z wydarzeniem TextChanged, jednak zdarzenie podstawowe ma przypisane atrybuty, aby zapobiec pojawianiu się w projektancie lub w systemie intellisense. Ponieważ używamy wydarzenie w naszej kontroli niestandardowych, jest to niepożądane, dlatego należy wykonać następujące czynności:

[Browsable(true)] 
    [EditorBrowsable(EditorBrowsableState.Always)] 
    public new event EventHandler TextChanged 
    { 
     add { base.TextChanged += value; } 
     remove { base.TextChanged -= value; } 
    } 

obsługi zdarzeń będzie jeszcze dodane do tego samego zdarzenia, bez względu na rodzaj odniesienia, który jest używany do niego dostęp , ale odwołania do typu pochodnego będą zawierały zdarzenie TextChanged w intellisense, a kiedy nasza kontrola zostanie umieszczona na formularzu w projektancie, zdarzenie TextChanged pojawi się w oknie właściwości.

0

Niedawno zdarzyło mi się, że słowo kluczowe "nowe" było całkiem przydatne. Pisałem testy jednostkowe dla metod w starszej bazie kodu, a niektóre metody z klas, na których pisałem testy, zawierały zewnętrzne zależności ukryte w metodach. Uwzględnienie szyderczych ram dla rozwiązania problemu wydawało się w tym czasie niepotrzebne, więc zamiast używać szyderczych ram odziedziczyłem klasę, którą chciałem przetestować w klasie testów jednostkowych. Jednak metoda nadrzędna nie była wirtualna, więc nie można jej przesłonić. Moim pierwszym rozwiązaniem było po prostu dodanie wirtualnego słowa kluczowego do oryginalnej metody, aby umożliwić jego przesłonięcie i testy zadziałały świetnie. Mimo że zadziałało, nie sądzę, że dodanie wirtualnych słów kluczowych do podpisów metod tylko ze względu na konieczność "wyśmiewania" tego w teście jednostkowym jest dobrą decyzją projektową (ale mogę się mylić). Zamiast tego użycie słowa kluczowego "nowe" w klasie potomnej umożliwiło mi "pozbyć się" zależności między metodami macierzystymi, ukrywając oryginalną metodę za pomocą metody ze statycznymi wartościami zwracanymi, zapisując w ten sposób szybkie testy jednostkowe dla pozostałych metod. jak przy zmianie metod innych niż wirtualne na wirtualne.

Generalnie nie jestem pewien, czy tę technikę można uznać za dobrą praktykę. Jednak użycie tego rodzaju techniki może być bardzo przydatne podczas pisania zestawu testów w celu ułatwienia refaktoryzacji podczas pracy ze starszym kodem z wieloma zależnościami i na początku nie ma zautomatyzowanego zestawu testów. Testy najprawdopodobniej zostaną poprawione po zakończeniu prac refaktoryzacyjnych.

EDYCJA: Ponieważ ukrywanie metody występuje tylko w prywatnej klasie dziedziczonej, nie powinno to powodować żadnych nieporozumień, które mogą wystąpić w innych przypadkach użycia.

Powiązane problemy