問題の説明
アイテムごとに異なる可能性がある一般的なFoo<T>
要素のコレクションを保存しようとしています。また、任意のを受け入れることができるT
ような関数もあります。上記のリストの各要素でこの関数を呼び出すことができるはずです。これらはすべて関数の有効なパラメーターであるためですが、このアイデアを C# コンパイラーに表現することはできないようです。DoSomething<T>(Foo<T>)
Foo<T>
T
私が知る限り、問題は、C# ではFoo<T>
binding なしで書くことができないため、そのようなリストを実際に表現できないことですT
。私が望むのは、Java のワイルドカード メカニズム ( Foo<?>
) のようなものです。このワイルドカード型が存在する疑似 C# では、次のようになります。
class Foo<T> {
// ...
}
static class Functions {
public static void DoSomething<T>(Foo<T> foo) {
// ...
}
public static void DoSomething(List<Foo<?>> list) {
foreach(Foo<?> item in list)
DoSomething(item);
}
}
このパターンは Java では有効ですが、C# で同じことを行うにはどうすればよいでしょうか? 以下の回答に投稿する解決策を見つけるために少し実験しましたが、もっと良い方法があるはずだと感じています。
注:私はすでにこの問題を実際のニーズに「十分に」解決しており、それを回避する方法(dynamic
タイプを使用するなど)を知っていますが、放棄しないより簡単な解決策があるかどうかを本当に知りたいです静的タイプの安全性。
object
以下で提案されているように、または非ジェネリックなスーパータイプを使用するだけでは、 Foo<T>
. ただし、これについては何も知らなくても、これは理にかなっていT
ます。たとえば、 を使用して、ある場所からFoo<T>
a を取得し、別の場所から a を取得して呼び出すと、コンパイラはすべての型が適切に機能することを認識します。List<T> list
T value
list.Add(value)
動機
なぜこのようなものが必要なのかと尋ねられたので、ほとんどの開発者の日常的な経験に少し近い例を作成しています。ユーザーが特定の型の値を操作できるようにする一連の UI コンポーネントを作成しているとします。
public interface IUiComponent<T> {
T Value { get; set; }
}
public class TextBox : IUiComponent<string> {
public string Value { get; set; }
}
public class DatePicker : IUiComponent<DateTime> {
public DateTime Value { get; set; }
}
Value プロパティとは別に、コンポーネントにはもちろん他の多くのメンバー (OnChange
イベントなど) があります。
次に、元に戻すシステムを追加しましょう。このために UI 要素自体を変更する必要はありません。関連するすべてのデータに既にアクセスできるためです。OnChange
イベントを接続するだけで、ユーザーが UI コンポーネントを変更するたびに、それぞれの値が保存されますIUiComponent<T>
(無駄ですが、物事をシンプルにしましょう)。値を保存するには、フォームでStack<T>
for eachを使用IUiComponent<T>
します。これらのリストには、IUiComponent<T>
as キーを使用してアクセスします。リストの保存方法の詳細は省略します (これが重要だと思われる場合は、実装を提供します)。
public class UndoEnabledForm {
public Stack<T> GetUndoStack<T>(IUiComponent<T> component) {
// Implementation left as an exercise to the reader :P
}
// Undo for ONE element. Note that this works and is typesafe,
// even though we don't know anything about T...
private void Undo<T>(IUiComponent<T> component) {
component.Value = GetHistory(component).Pop();
}
// ...but how do we implement undoing ALL components?
// Using Pseudo-C# once more:
public void Undo(List<IUiComponent<?>> components) {
foreach(IUiComponent<?> component in components)
Undo(component);
}
}
Undo<T>()
すべてのIUiComponent
s を (名前で)直接呼び出すことで、すべてを元に戻すことができます。
public void Undo(List<IUiComponent<?>> components) {
Undo(m_TextBox);
Undo(m_DatePicker);
// ...
}
ただし、コンポーネントを追加/削除すると、コード内のもう 1 つの場所に触れる必要があるため、これは避けたいと思います。数十のフィールドと、すべてのコンポーネントで実行したい機能 (たとえば、すべての値をデータベースに書き込んで再度取得する) がある場合、これは多くの重複になる可能性があります。
サンプルコード
これは、ソリューションの開発/チェックに使用できる小さなコードです。タスクは、いくつかのPair<T>
-objects をある種のコレクション オブジェクトに入れ、次に、このコレクション オブジェクトを受け入れてそれぞれのFirst
andSecond
フィールドを交換する関数を呼び出すことですPair<T>
( を使用Application.Swap()
)。理想的には、キャストやリフレクションを使用しないでください。Pair<T>
-class をまったく変更せずにそれを行うことができれば、ボーナスポイント:)
class Pair<T> {
public T First, Second;
public override string ToString() {
return String.Format("({0},{1})", First, Second);
}
}
static class Application {
static void Swap<T>(Pair<T> pair) {
T temp = pair.First;
pair.First = pair.Second;
pair.Second = temp;
}
static void Main() {
Pair<int> pair1 = new Pair<int> { First = 1, Second = 2 };
Pair<string> pair2 = new Pair<string> { First = "first", Second = "second" };
// imagine more pairs here
// Silly solution
Swap(pair1);
Swap(pair2);
// Check result
Console.WriteLine(pair1);
Console.WriteLine(pair2);
Console.ReadLine();
}
}