2012-06-21 7 views
11

Mam dwa różne rodzaje ciągów, które przechodzę i wykorzystuję w moim kodzie, i dwa są ze sobą ściśle powiązane, ale nie powinny być ze sobą powiązane. Pomyślałem, że mógłbym pomóc sobie uniknąć błędów, mając dwie klasy, które są tylko ciągami, ale z różnymi nazwami tak, że sygnatury metod (i ogólnie niekompatybilność) wymusiliby semantyczne znaczenie dwóch różnych rodzajów łańcuchów. W tym samym czasie nie chciałem mieć refaktoryzatora od something = "foo"; do something.Value = "foo"; w stu miejscach.Jak deklarować pochodne klasy "powłoki", które nic nie robią, ale działają jako nazwy?

Pierwsza myśl:

private class FirstKind : string { } 
private class SecondKind : string { } 

Pomysł jest, że jeśli mam

void MyMethod(FirstKind varOne, SecondKind varTwo) {...} 

, a następnie spróbuj wywołać ją z MyMethod(secondKindVar, firstKindVar);, to pojawia się błąd kompilatora.

Ściana, którą wybrałem: string jest zaplombowana.

Druga myśl: stwórz ogólną klasę KindOf<T>, która nie zrobiłaby niczego oprócz przyjmowania i wypluwania wartości za pomocą niejawnych operatorów konwersji. Tak:

private class KindOf<T> 
{ 
    public T Value { get; set; } 
    public KindOf() { Value = default(T); } 
    public KindOf(T val) { Value = val; } 
    public static implicit operator T(KindOf<T> kindOf) { return kindOf.Value; } 
    public static implicit operator KindOf<T>(T value) { return new KindOf<T>(value); } 
} 

ten sposób mógłby zrobić coś takiego:

private class FirstKind : KindOf<string> { } 
private class SecondKind : KindOf<string> { } 

ścienny uderzę: Nic z klasy KindOf<T> wydaje się dziedziczyć: ani konstruktorzy ani operatorzy występują we wszystkich typach pochodnych, co oznacza, Muszę ponownie wdrożyć zasadniczo całą klasę bazową w klasach pochodnych. Kopiowanie kodu. Fuj.

Czuję, że brakuje tu czegoś podstawowego, a może jestem w chwastach. Jakieś pomysły na to, co próbuję zrobić?

+1

Może mógłbyś wyjaśnić, co oznaczają "dwa różne rodzaje strun"? –

+1

Poza tym, może być konieczne zaimplementowanie niejawnych operatorów i konstruktorów w podklasach. Tak, coś w rodzaju kopiowania kodu, ale przynajmniej nie używa zawiłego procesu lub dziedziczenia, gdy dziedziczenie tak naprawdę nie komunikuje niczego realnego (retorycznie, co to jest "KindOf" i czy to naprawdę ma znaczenie?) I jest po prostu aby zredukować duplikację kodu na klasach, które nie mają nic wspólnego poza podobną składnią (ale różnymi zastosowaniami) –

+0

Uzgodnione, klasy naprawdę nie powinny być w tej samej hierarchii dziedziczenia, szczególnie biorąc pod uwagę, że nie * chcesz *, aby się do nich odnosić przez wspólny typ, chcesz, aby pozostały odrębne. – Servy

Odpowiedz

0

Po prostu robię to dla ciągów.

abstract class MyTypedString 
{ 
    protected MyTypedString(string value) 
    { 
    Value = value; 
    } 
    public string Value { get; private set; } 
} 

Następnie można wykończyć tę klasę abstrakcyjną ze swoimi FirstKind i SecondKind. Dzięki temu obiekt będzie niezmienny, podobnie jak łańcuchy. Nie dałbym tego żadnym niejawnym operatorom konwersji.

+0

To zasadniczo taki sam jak jego drugi przykład (tylko mniej ładnie). –

0

Operatorzy nadal będą działać, wystarczy odwołać członka .Value swojego kindof zajęć:

FirstKind stringFoo = new FirstKind("foo"); 
FirstKind stringQux = new FirstKind("qux"); 

// String concatenation operator 
FirstKind stringTar = new FirstKind(stringFoo.Value + stringQux.Value); 

Jednak jak mówisz, nie będzie „typ sprawdzania”, jeśli zmieszać dwa, na przykład

SecondKind stringBar = new SecondKind("bar"); 
FirstKind stringHal = new FirstKind(stringFoo.Value + stringBar.Value); // this will succeed, even though you intend stringFoo and stringBar to be incompatible 

Jako operatorzy są częścią klasy interface następnie należy ponownie je realizować - jednak można po prostu zawinąć zawartej klasy wdrażania, więc trzeba tylko zrobić nudny grunt-praca uzyskać to do pracy .

0

Możesz spróbować przeładować operatora konwersji implicit w swojej klasie na ciąg znaków. Wtedy nie musiałbyś aktualizować swoich odniesień do .Value. Klasy będą musiały pozostać oddzielne i będziesz musiał stworzyć konstruktory dla nich, które przyjmą wartość ciągu. Najlepszą praktyką byłoby zachowanie ciągu znaków readonly, aby klasa stała się niezmienna. Naśladując klasę ciągów, ma to być reprezentacja.

public class FirstKind 
{ 
     private readonly string value; 
     public FirstKind(string v) { this.value = v }; 
     public static implicit operator string(FirstKind v){ return v.value; } 
} 

public class SecondKind 
{ 
     private readonly string value; 
     public SecondKind (string v) { this.value = v }; 
     public static implicit operator string(SecondKind v){ return v.value; } 
} 
+1

On już ma ... – Servy

+0

Przypadkowo opublikowane, zanim skończyłem. – Fr33dan

+0

On już to robi ... – Servy

0

Możesz prawie używać swojej ogólnej klasy. Tylko niejawne rzutowanie z łańcucha znaków (lub innego typu) na klasę wyniszczoną nigdy nie zadziała, ponieważ klasa podstawowa nie może wiedzieć, czy łańcuch powinien być rzutowany na pierwszemu lub drugi raz. Tak że część powinna być kopiowane, reszta jednak może pozostać w klasie bazowej:

private class FirstKind : KindOf<string> 
{ 
    public static implicit operator FirstKind(string value) { return new FirstKind{Value = value}; } 
} 
private class SecondKind : KindOf<string> 
{ 
    public static implicit operator SecondKind(string value) { return new SecondKind{Value = value}; } 
} 

Casting do klasy bazowej jeszcze można zrobić od wewnątrz klasy generycznej:

 FirstKind Foo = "Foo"; //via implicit operator in FirstKind 
     string stringFoo = Foo; //via implicit operator in baseclass 
+0

Problem pozostaje, ale nie można zrobić czegoś takiego jak "Foo.Length". Trzeba by naprawić cały kod, by robić rzeczy takie jak 'Foo.Value.Length', co prawdopodobnie nie jest warte wszystkich kłopotów. –

+0

Tak, to z kolei wymagałoby między KindOfString klasy, która dziedziczy z KindOf i implementuje te właściwości, z których inne rodzaje dziedziczą (lub jeśli tylko ciągi są mimo to używane, pomiń ogólnie ogólną klasę). –

3

Aby być uczciwym, Nie użyłbym w ogóle implicytnych operatorów ani żadnych dziedziczek. Cała idea polega na zastosowaniu metody, która wymaga FirstKind. Dzięki niejawnemu operatorowi możesz po prostu przekazać ciąg znaków, który zostanie przekonwertowany; oznacza to, że nie zmuszasz kodu do wyraźnego stwierdzenia, że ​​jest to pierwszy/drugi rodzaj. Trzymałbym dwie klasy, z których każda ma tylko konstruktora pobierającego ciąg i własność tylko do odczytu. Komplikujesz to, dodając cokolwiek innego.

W przypadku korzystania z tej nieruchomości jest coraz irytujące, mógłbym zobaczyć niejawna konwersja do ciąg, z każdej z klas niestandardowych, ale wierzę, że niejawna konwersja z ciąg byłoby szkodliwe . Chociaż zgadzam się, że należy zasadniczo unikać kopiowania/wklejania kodu, gdy jest to dosłownie jedna linia kodu, kopiowanie wklejania, które niejawnie przekształca się w obie klasy, będzie łatwiejsze, bardziej skuteczne i ogólnie łatwiejsze do utrzymania niż próba użycia dziedziczenia lub czegokolwiek innego. aby nie pisać go dwa razy.

+0

+1 Niestety, ponieważ 'String' jest zapieczętowany, obawiam się, że jest to prawdopodobnie najlepsza opcja. –

+0

Zgadzamy się na to. "Implicit conversion to string" było moim pomysłem, ale nadal trzymam klasy oddzielnie, jak sugerujesz. – Fr33dan

1

Zamierzam uczynić mój komentarz odpowiedzią, ponieważ uważam, że jest tak samo.

Z góry mojej głowy może zaistnieć potrzeba implementacji niejawnych operatorów i konstruktorów w podklasach. Tak, coś w rodzaju kopiowania kodu, ale przynajmniej nie wykorzystuje zawiłego procesu lub dziedziczenia, gdy dziedziczenie tak naprawdę nie komunikuje niczego rzeczywistego (retorycznie, co to jest "KindOf" i czy to naprawdę znaczy cokolwiek?) I jest po prostu tam, aby zmniejszyć powielania kodu poprzek klas, które nie mają nic wspólnego oprócz podobnej składni (ale różnych zastosowań)

public class FirstKind 
{ 
    public string Value { get; private set; } 

    public FirstKind(string value) 
    { 
     this.Value = value; 
    } 

    public static implicit operator FirstKind(string value) 
    { 
     return new FirstKind(value); 
    } 

    public static implicit operator string(FirstKind value) 
    { 
     return value.Value; 
    } 
} 

Ponadto, warto rozważyć klasa niezmienna jeśli oni mają reprezentować ciągi. (inny szczegół implementacji, który może wskazywać na to, że te obiekty nie powinny dziedziczyć z ponownie użytej klasy KindOf, która w twojej implementacji powoduje, że są zawsze zmienne)

Nie ma zbyt wiele przewodów i pozostawia twój kod otwarty na zmiany w przyszłości (być może chcesz dodać nową właściwość? Dodaj kod weryfikacyjny?) i konsumenci API naprawdę nie dbają o to, że cokolwiek jest KindOf, aby zaoszczędzić na pewnym powieleniu kodu . (Być może można zrobić fragment, aby twoje życie trochę łatwiejsze?)

1

w swoim pytaniu, FirstKind i SecondKind są rodzaje zmiennych łańcuchowych, jak i string jest argument typu klasy KindOf<T>.Odmienne podejście (co pozwala uniknąć powielania kodu wymaganego przez innych odpowiedzi) znajduje się odwrócić rolę FirstKind i SecondKind, czyniąc je opróżnić „marker” klas, które są używane jako argumenty dla typu String<TKind>:

public class FirstKind { } 
public class SecondKind { } 

public struct String<TKind> 
{ 
    private readonly string _value; 

    public String(string value) 
    { 
     _value = value; 
    } 

    public override string ToString() 
    { 
     return _value; 
    } 

    public static implicit operator String<TKind>(string s) // see the note below 
    { 
     return new String<TKind>(s); 
    } 

    public static implicit operator string(String<TKind> s) 
    { 
     return s.ToString(); 
    } 

    public static String<TKind> operator +(String<TKind> s1, String<TKind> s2) 
    { 
     return new String<TKind>(s1.ToString() + s2.ToString()); 
    } 

    public int Length 
    { 
     get { return _value.Length; } 
    } 

    // You can add methods like Substring and Trim that delegate to _value. 
} 

teraz można zadeklarować MyMethod takiego:

void MyMethod(String<FirstKind> varOne, String<SecondKind> varTwo) 
{ 
    varOne += varOne; // OK 
    varTwo += varTwo; // OK 
    varOne = varTwo; // ERROR 
} 

Uwaga: Zostawiłam w niejawny operator konwersji z prostym string. Możesz rozważyć jego usunięcie, zmuszając kod do wyraźnego zidentyfikowania rodzaju ciągu: new String<FirstKind>("...").

+0

Hm, ciekawe podejście. Muszę to rozważyć. – Atario

Powiązane problemy