11

コードで渡したり使用したりする 2 種類の文字列があります。この 2 つは密接に関連していますが、混同しないでください。メソッド シグネチャ (および一般的には型の非互換性) が 2 つの異なる種類の文字列のセマンティックな意味を強制するように、単なる文字列であるが名前が異なる 2 つのクラスを使用することで、エラーを回避できると考えました。同時に、何百もの場所をリファクタリングsomething = "foo";する必要はありませんでした。something.Value = "foo";

最初の考え:

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

もし私が持っているなら

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

で呼び出しようとするとMyMethod(secondKindVar, firstKindVar);、コンパイラ エラーが発生します。

ぶつけた壁: string封印。

再考:KindOf<T>暗黙の変換演算子を使用して、値を取り込んで吐き出すだけのジェネリック クラスを作成します。このような:

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

このようにして、次のようなことができます:

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

私がぶつかった壁:KindOf<T>クラスから何も継承していないようです: 派生型にはコンストラクターも演算子も存在しません。つまり、基本的に派生クラスの基本クラス全体を再実装する必要があります。コードのコピー。うん。

だから私はここで基本的な何かが欠けているように感じます. 私がやろうとしていることについてのアイデアはありますか?

4

7 に答える 7

3

正直なところ、私は暗黙の演算子をまったく使用したり、継承を使用したりしません。全体のアイデアは、 を取るメソッドを持つことFirstKindです。暗黙の演算子を使用すると、文字列を渡すだけで変換されます。これは、コードが第 1 種/第 2 種であることを明示的に宣言することを強制していないことを意味します。それぞれが文字列と読み取り専用プロパティを受け取るコンストラクターだけを持つ 2 つのクラスに固執します。他に何かを追加して複雑にしすぎています。

そのプロパティを使用するのが煩わしい場合は、各カスタム クラスから文字列への暗黙的な変換が見られる可能性がありますが、文字列からの暗黙的な変換は有害であると確信しています。コードのコピー/貼り付けは一般的に避けるべきであることに同意しますが、それが文字通り1行のコードである場合、両方のクラスへの暗黙的な変換をコピーして貼り付ける方が、継承などを使用するよりも簡単で効果的であり、一般的に保守性が高くなります。二度書きしないように。

于 2012-06-21T14:58:53.817 に答える
1

私はそれが1つであると思うので、私のコメントを答えにするつもりです。

私の頭の上では、暗黙の演算子とコンストラクターをサブクラスに実装する必要があるだけかもしれません。ええ、コードのコピーのようなものですが、少なくとも複雑なプロセスを採用したり、継承が実際に何も伝えていない場合に継承を使用したりしていません (修辞的に、「KindOf」とは何か、それは本当に何かを意味しますか?) そしてただ類似した構文以外に共通点がない (ただし用途が異なる) クラス間でのコードの重複を減らすためにあります。

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

また、クラスが文字列を表す場合は、クラスを不変にすることを検討することもできます。(おそらくこれらのオブジェクトを指す可能性のある別の実装の詳細は、再利用されたクラスから継承してはならないためKindOf、実装では常に変更可能になります)

それは多くの配線ではなく、将来の変更のためにコードを開いたままにしておきます (おそらく、新しいプロパティを追加したいですか? 検証コードを追加しますか?) API の消費者は、マイナーなKindOfものを節約するためだけのものであることを本当に気にしません。コードの複製。(おそらく、あなたの人生を少し楽にするためにスニペットを作ることができますか?)

于 2012-06-21T14:59:25.800 に答える
1

あなたの質問でFirstKindSecondKind、文字列のような変数の型であり、クラスstringの型引数です。KindOf<T>別のアプローチ(他の回答で必要なコードの重複を回避する)は、FirstKindandの役割を反転しSecondKind、型の型引数として使用される空の「マーカー」クラスにすることです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.
}

MyMethodこれで、次のように宣言できます。

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

注:プレーンからの暗黙的な変換演算子を残しましたstring。コードに文字列の種類を明示的に識別するように強制して、それを削除することを検討することをお勧めします: new String<FirstKind>("...").

于 2012-06-21T15:04:22.827 に答える
0

ジェネリッククラスはほぼ使用できます。文字列(または他の型)から派生クラスへの暗黙的なキャストのみが機能することはありません。これは、基本クラスが文字列をfirstkindまたはsecondkindのどちらにキャストする必要があるかを認識できないためです。その部分をコピーする必要がありますが、残りは基本クラスにとどまることができます。

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

基本クラスへのキャストは、ジェネリッククラス内から引き続き実行できます。

        FirstKind Foo = "Foo"; //via implicit operator in FirstKind
        string stringFoo = Foo; //via implicit operator in baseclass
于 2012-06-21T14:55:27.560 に答える
0

演算子は引き続き機能し.Valueます。KindOf クラスのメンバーを参照するだけで済みます。

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

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

ただし、あなたが言うように、2つを混在させると「型チェック」は行われません。

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

演算子はクラスのインターフェースの一部であるため、それらを再実装する必要がありますが、含まれているクラスの実装をラップするだけでよいため、これを機能させるために退屈な面倒な作業を行うだけで済みます。

于 2012-06-21T14:53:53.223 に答える
0

クラスの暗黙的な変換演算子を文字列にオーバーロードしてみてください。次に、.Value への参照を更新する必要はありません。クラスは別個のままにしておく必要があり、文字列値を受け入れるコンストラクターを作成する必要があります。ベスト プラクティスはreadonly、クラスが不変になるように文字列を保持することです。表現することを意図した文字列クラスを模倣します。

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; }
}
于 2012-06-21T14:54:32.210 に答える
0

私は文字列に対してこれを行うだけです。

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

FirstKind次に、この抽象クラスをandで終了できますSecondKind。これにより、文字列のようにオブジェクトが不変になります。これには暗黙の変換演算子は与えません。

于 2012-06-21T14:50:43.247 に答える