ジェネリックメソッド複数(OR)タイプ制約


135

これを読んで、メソッドをジェネリックメソッドにすることで、メソッドが複数の型のパラメーターを受け入れることができるようになることを学びました。この例では、次のコードをタイプ制約とともに使用して、「U」がであることを確認していIEnumerable<T>ます。

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

次のような複数の型制約を追加できるいくつかのコードを見つけました。

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

しかし、このコードは、それが強制するように見えるargのタイプの両方でなければならないParentClass ChildClass。私がしたいのは、argが次のようなタイプParentClass または ChildClass次の方法である可能性があるということです。

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

あなたの助けはいつものように感謝されます!


4
そのようなメソッドの本体内で一般的な方法で何が便利にできますか(複数の型がすべて特定の基本クラスから派生している場合を除き、その場合は型制約として宣言しないでください)?
Damien_The_Unbeliever

@Damien_The_Unbeliever身体で何を意味しているのかわからない?型を許可して手動で本文をチェックするつもりでない限り、そして私が書きたい実際のコード(最後のコードスニペット)では、文字列OR例外を渡すことができるようにしたいので、クラスはありませんそこの関係(私が想像するsystem.objectを除く)。
マンスフィールド2012年

1
また、書き込みには使用がないことに注意where T : stringとして、stringある密封されたクラスが。以下の彼の回答で@ Botz3000によって説明されているように、あなたができる唯一の有用なことは、stringとのオーバーロードを定義することですT : Exception
Mattias Buelens、2012年

しかし、関係がない場合、呼び出すことができるメソッドargは、定義されたメソッドだけです。それでは、objectジェネリックを画像から削除して、型を作成しないのはなぜarg objectですか。あなたは何を得ていますか?
Damien_The_Unbeliever

1
@Mansfield オブジェクトパラメータを受け入れるプライベートメソッドを作成できます。どちらのオーバーロードもそれを呼び出します。ここではジェネリックは必要ありません。
Botz3000 2012年

回答:


68

それは不可能です。ただし、特定のタイプのオーバーロードを定義できます。

public void test(string a, string arg);
public void test(string a, Exception arg);

それらがジェネリッククラスの一部である場合、それらはメソッドのジェネリックバージョンよりも優先されます。


1
面白い。これは私には起こりましたが、1つの関数を使用することでコードがよりクリーンになる可能性があると思いました。ああ、どうもありがとう!好奇心から、これが不可能である特定の理由があるかどうか知っていますか?(意図的に言語から除外されていますか?)
マンスフィールド

3
@Mansfield正確な理由はわかりませんが、ジェネリックパラメーターを意味のある方法で使用できなくなると思います。クラス内では、完全に異なる型であることが許可されている場合、それらはオブジェクトのように扱われる必要があります。つまり、ジェネリックパラメーターを省略して、オーバーロードを提供することもできます。
Botz3000 2012年

3
@マンスフィールド、それはor関係が便利になるために物事を一般的に遠くするからです。あなたは思い持って何をすべきかを把握し、すべてのことにリフレクションを行うこと。(YUCK!)。
Chris Pfohl、2012年

29

ボッツの答えは100%正解です、ここに簡単な説明があります:

メソッド(ジェネリックかどうかにかかわらず)を記述し、メソッドが取るパラメーターの型を宣言するときは、コントラクトを定義します。

Type Tが行う方法を知っている一連のことを行う方法を知っているオブジェクトを私に提供した場合、「a」:宣言した型の戻り値、または「b」:を使用するある種の動作のいずれかを提供できます。そのタイプ。

(orを使用して)一度に複数のタイプを指定しようとした場合、またはコントラクトが曖昧になる複数のタイプの値を返すように取得しようとした場合:

縄跳びの方法を知っているオブジェクト、または15桁目のpiを計算する方法を知っているオブジェクトを受け取った場合、釣りに行くことができるオブジェクト、またはコンクリートを混ぜることができるオブジェクトを返します。

問題は、メソッドに入ったときに、それらがあなたにIJumpRopeかを与えたかどうかわからないということですPiFactory。さらに、先に進んでメソッドを使用する場合(魔法のようにコンパイルできると想定している場合)、Fisherまたはがあるかどうかは本当にわかりませんAbstractConcreteMixer。基本的にそれは全体をより混乱させます。

問題の解決策は、次の2つの可能性のうちの1つです。

  1. 可能な変換、動作などをそれぞれ定義する複数のメソッドを定義します。それがボッツの答えです。プログラミングの世界では、これはメソッドのオーバーロードと呼ばれます。

  2. メソッドに必要なすべてのことを実行する方法を知っている基本クラスまたはインターフェースを定義し、1つのメソッドがそのタイプのみを取得するようします。これには、stringExceptionを小さなクラスにまとめて、それらを実装にマッピングする方法を定義する必要がありますが、すべてが非常に明確で読みやすくなっています。今から4年後、あなたのコードを読んで、何が起こっているのか簡単に理解できました。

どちらを選択するかは、選択1と2がどれほど複雑で、拡張可能である必要があるかによって異なります。

したがって、あなたの特定の状況では、例外からメッセージまたは何かを引き出しているだけだと想像します。

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

必要なのは、a stringとan ExceptionをIHasMessageに変換するメソッドだけです。非常に簡単。


申し訳ありませんが、@ Botz3000、名前のスペルを間違えたことに気づきました。
Chris Pfohl

または、コンパイラが関数内の和集合型として型を脅かし、戻り値が取得した型と同じ型になる可能性があります。TypeScriptがこれを行います。
Alex

@Alexしかし、それはC#が行うことではありません。
Chris Pfohl

それは本当ですが、それは可能です。私はこの答えを読むことができないと読んだのですが、誤解しましたか?
Alex

1
実は、C#のジェネリックパラメーター制約とジェネリック自体は、たとえばC ++テンプレートと比べるとかなり原始的です。C#では、ジェネリック型で許可される操作をコンパイラに事前に通知する必要があります。その情報を提供する方法は、実装インターフェイス制約を追加することです(ここでT:IDisposable)。しかし、ジェネリックメソッドを使用するために型にインターフェイスを実装させたくない場合や、共通のインターフェイスを持たないジェネリックコードで一部の型を許可したい場合があります。例 構造体または文字列を許可して、値に基づく比較のためにEquals(v1、v2)を呼び出すだけで済むようにします。
Vakhtang 2018

8

ChildClassがParentClassから派生していることを意味する場合、ParentClassとChildClassの両方を受け入れるには、次のように記述します。

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

一方、継承関係のない2つの異なる型を使用する場合は、同じインターフェイスを実装する型を検討する必要があります。

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

次に、ジェネリック関数を次のように記述できます。

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}

上記のコメントにある封印されたクラスである文字列を使用したいので、これができるとは思わないことを除いて、良い考えです...
Mansfield

これは実際には設計上の問題です。汎用関数は、コードを再利用して同様の操作を行うために使用されます。メソッド本体で異なる操作を行うことを計画している場合はどちらも、メソッドを分離する方がより良い方法です(IMHO)。
12

実際、私がやっていることは、単純なエラーロギング関数を作成することです。最後のパラメーターをエラーに関する情報の文字列または例外にしたい場合は、とにかくe.message + e.stacktraceを文字列として保存します。
マンスフィールド2012年

isSuccesful、保存メッセージ、例外をプロパティとして持つ新しいクラスを書くことができます。次に、issuccesful trueかどうかを確認し、残りを実行できます。
12

1

この質問と同じくらい古いのですが、私は上記の私の説明に対してまだランダムな賛成票を得ています。説明はそのままでも完全にうまく機能しますが、私はもう一度、ユニオンタイプの代わりとして役立つタイプ(C#では直接サポートされていない質問への強く型付けされた回答)で回答します)。

using System;
using System.Diagnostics;

namespace Union {
    [DebuggerDisplay("{currType}: {ToString()}")]
    public struct Either<TP, TA> {
        enum CurrType {
            Neither = 0,
            Primary,
            Alternate,
        }
        private readonly CurrType currType;
        private readonly TP primary;
        private readonly TA alternate;

        public bool IsNeither => currType == CurrType.Primary;
        public bool IsPrimary => currType == CurrType.Primary;
        public bool IsAlternate => currType == CurrType.Alternate;

        public static implicit operator Either<TP, TA>(TP val) => new Either<TP, TA>(val);

        public static implicit operator Either<TP, TA>(TA val) => new Either<TP, TA>(val);

        public static implicit operator TP(Either<TP, TA> @this) => @this.Primary;

        public static implicit operator TA(Either<TP, TA> @this) => @this.Alternate;

        public override string ToString() {
            string description = IsNeither ? "" :
                $": {(IsPrimary ? typeof(TP).Name : typeof(TA).Name)}";
            return $"{currType.ToString("")}{description}";
        }

        public Either(TP val) {
            currType = CurrType.Primary;
            primary = val;
            alternate = default(TA);
        }

        public Either(TA val) {
            currType = CurrType.Alternate;
            alternate = val;
            primary = default(TP);
        }

        public TP Primary {
            get {
                Validate(CurrType.Primary);
                return primary;
            }
        }

        public TA Alternate {
            get {
                Validate(CurrType.Alternate);
                return alternate;
            }
        }

        private void Validate(CurrType desiredType) {
            if (desiredType != currType) {
                throw new InvalidOperationException($"Attempting to get {desiredType} when {currType} is set");
            }
        }
    }
}

上記のクラスは、TP または TAのいずれかになるタイプを表します。あなたはそれをそのまま使うことができます(タイプは私の元の答えを参照しています):

// ...
public static Either<FishingBot, ConcreteMixer> DemoFunc(Either<JumpRope, PiCalculator> arg) {
  if (arg.IsPrimary) {
    return new FishingBot(arg.Primary);
  }
  return new ConcreteMixer(arg.Secondary);
}

// elsewhere:

var fishBotOrConcreteMixer = DemoFunc(new JumpRope());
var fishBotOrConcreteMixer = DemoFunc(new PiCalculator());

重要なメモ:

  • チェックしないとランタイムエラーが発生します IsPrimary最初に。
  • IsNeither IsPrimaryまたはのいずれかをチェックできますIsAlternateます。
  • Primaryおよびを介して値にアクセスできますAlternate
  • TP / TAとEitherの間に暗黙的なコンバーターがあり、値またはEither期待される場所に値を渡すことができます。あなたがいる場合行う渡しEitherどこTAかがTP期待されますが、Either値の間違った型が含まれていますが、実行時エラーが発生します。

通常、これを使用して、メソッドが結果またはエラーを返すようにします。それは本当にそのスタイルコードをクリーンアップします。また、非常にまれに(まれに)これをメソッドのオーバーロードの代わりとして使用します。現実的には、これはそのような過負荷の非常に悪い代替品です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.