2013-05-14 10 views
5

Zasadniczo mam następujący scenariusz:Czy można programowo wymusić klasa pochodna, aby przekazać się do klasy bazowej jako typ ogólny?

public abstract class FooBase<T> where T : FooBase<T> 
{ 
    public bool IsSpecial { get; private set; } 

    public static T GetSpecialInstance() 
    { 
     return new T() { IsSpecial = true }; 
    } 
} 

public sealed class ConcreteFooA : FooBase<ConcreteFooA> { ... } 
public sealed class ConcreteFooB : FooBase<ConcreteFooB> { ... } 

ale problem widzę tutaj jest to, że mogłem zrobić ConcreteFooB : FooBase<ConcreteFooA> { ... }, które całkowicie bałagan klasy w czasie wykonywania (nie spełniałby logikę I próbuję osiągnąć), ale nadal poprawnie się kompiluje.

Czy jest jakiś sposób, w jaki nie myślałem o wymuszeniu generycznego, aby być czymkolwiek, co jest klasą pochodną?


Aktualizacja: I nie kończy się za pomocą parametru rodzajowego, T, w klasie FooBase<T>, ja po prostu nie wymienić każdą metodę, która ma go jako out i parametr, ale mam zastosowanie dla T.

+1

Wyrzuć wyjątek, jeśli w typie "typeof (this)! = Typeof (T)", w konstruktorze? A może szukasz rozwiązania w czasie kompilacji? – Oded

+3

Jest to tak zwany dziwnie powtarzający się wzorzec szablonu lub ciekawy powtarzający się ogólny wzór. –

+0

@DanielHilgarth - Nie myślałem tak daleko przed nami;) – Oded

Odpowiedz

3

aby odpowiedzieć na to pytanie:

nie, nie ma rozwiązania czas kompilacji do egzekwowania tego.

0

Nie wiem, dlaczego masz to jako wymaganie. Najpierw zasugerowałbym, abyś wrócił i spojrzał na "twój model obiektowy i określił, dlaczego uważasz, że potrzebujesz tego wymagania i określa, czy istnieje lepszy sposób osiągnięcia tego, co chcesz osiągnąć.

Myślę, że widzę jeden problem z tym, co masz powyżej: brak ogólnych parametrów w twoich definicjach/deklaracjach klas ConcreteFooA i ConcreteFooB.

Wygląda na to, że lepiej jest utworzyć interfejs IFooBase i wdrożyć konkretne implementacje interfejsu. W każdym przypadku, w którym chcesz pracować z IFooBase, możesz użyć zmiennej typu IFooBase.

Więc:

public interface IFooBase { /* Interface contract... */ } 

public class ConcreteFooA : IFooBase { /* Implement interface contract */ } 
public class ConcreteFooB : IFooBase { /* Implement interface contract */ } 

// Some class that acts on IFooBases 
public class ActionClass 
{ 
    public ActionClass(IFooBase fooBase) { this._fooBase = foobase }; 

    public DoSomething() { /* Do something useful with the FooBase */ } 

    // Or, you could use method injection on static methods... 
    public static void DoSomething(IFooBase fooBase) { /* Do some stuff... */ } 
} 

Zaledwie kilka pomysłów. Ale nie sądzę, że możesz osiągnąć to, co chcesz zrobić z samym generics.

+0

Ten rodzaj struktury klasowej jest powszechnie stosowany w płynnych interfejsach. –

+0

Mam zastosowanie dla T w klasie bazowej, po prostu nie zamieściłem wszystkich moich metod w mojej klasie bazowej i zamiast tego wstawiłem znaki elipsy wskazujące, że w klasie jest coś takiego. – michael

+0

Widzę ... hmm. Dobry punkt Daniel Hilgarth ... '' IQueryable' na jeden, przypuszczam;) Duh .. – fourpastmidnight

1

Istnieje kilka sposobów, aby egzekwować tę zasadę:

  1. Unit Testing - Można napisać badanej jednostki (lub testów jednostkowych), aby upewnić się, że zebrane rodzaje mijają się jako parametru rodzajowego .
  2. Analiza kodu - Można utworzyć niestandardową regułę analizy kodu, która wymusza to, a następnie ustawić tę regułę jako błąd (w porównaniu z ostrzeżeniem). Zostałoby to sprawdzone podczas kompilacji.
  3. Reguła FxCop - podobna do reguły Code Analysis, chyba że nie masz wersji Visual Studio, która ma wbudowaną obsługę analizy kodu, możesz zamiast tego użyć FxCop.

Oczywiście żadna z tych reguł nie jest wymuszana na standardowej kompilacji, ale wymaga dodatkowych narzędzi (Testowanie jednostek, Analiza kodu, FxCop). Jeśli ktoś zabrał twój kod i skompilował go bez użycia tych narzędzi, natknąłbyś się na ten sam problem ... oczywiście, w tym momencie, dlaczego ktoś inny kompiluje twój kod bez uruchamiania twoich testów jednostkowych lub reguł analizy kodu/FxCop?


Alternatywnie, a tego nie polecam, można rzucić błąd podczas działania. Dlaczego nie? Według Microsoftu:

Jeśli statyczny konstruktor zgłasza wyjątek, środowisko wykonawcze nie będzie wywołać ją po raz drugi, a typ pozostanie niezainicjowany dla żywotność domeny aplikacji, w których program jest bieganie .

To naprawdę nie rozwiąże problemu. Ponadto wyrzucenie wyjątku podczas inicjalizacji statycznej stanowi naruszenie Analizy kodu CA1065:DoNotRaiseExceptionsInUnexpectedLocations. Kierujesz się w złym kierunku, jeśli to zrobisz.

1

Nie ma sposobu na kompilację tego, o ile wiem. Może być jednak wymuszony przy użyciu sprawdzania w czasie działania. Żadne nietypowe działania użytkownika zwykle nie byłyby w stanie tego spowodować (po prostu nieprawidłowe kodowanie), więc jest podobne do posiadania Debug.Assert w miejscach (i, w rzeczywistości, można to zaimplementować za pomocą tego, jeśli chcesz). Na przykład.

public abstract class FooBase<T> where T : FooBase<T> 
{ 
    protected FooBase() 
    { 
     Debug.Assert(this.GetType() == typeof(T)); 
    } 
} 
0

Nie jest to możliwe, nie powinny mieć, ponieważ zgodnie z L w postaci ciała stałego:

Liskov zasada substytucja „obiektów w programie powinna być wymienialna przypadkach ich podtypy bez zmiany Słuszność tego program".

Tak więc kompilator robi to, co powinien.

Może trzeba zmienić projekt i realizację swoich klas, na przykład, stosując wzór behawioralny. Na przykład, jeśli obiekt powinien przedstawiać różne algorytmy dla określonego obliczenia, można użyć Strategy Pattern.

Ale nie mogę o tym poradzić, ponieważ nie wiem, co dokładnie chcesz osiągnąć.

Powiązane problemy