web-dev-qa-db-ja.com

不明なジェネリックリストを返す<T>

そしてどんな援助にも感謝します。

メソッドから未知のGeneric.List型を返すにはどうすればよいですか。

public void Main()
{
  List<A> a= GetData("A");   
}

public List<T> GetData(string listType)
{
   if(listType == "A")
   {
     List<A> a= new List<A>() 
     ...
     return a; 
   }
   else
   {
     List<B> b = new List<B>()
     return b;

   }
}

次の例では、次のようなエラーが表示されます:変換できませんList<A>からList<T>

これは可能ですか?エラーは「戻り値a」で発生します。コードの行。
また、ラインでエラーが発生しないようにするために何をする必要がありますか:

List<A> a= GetData("A");   

おかげで、スティーブン

18

List<T>の代わりにIListを使用してください。

25
John Rasch

オブジェクトのリストを返すことに限定される代わりに、AとBが共通の基本型から確実に派生するか、共通のインターフェースを実装してから、その基本型またはインターフェースのリストを返すことになります。その効果にジェネリックメソッドに制約を含めます。

List<ICommon> GetData<T>() where T: ICommon
{

}
13
AnthonyWJones

このようなList<T>を直接返すことはできません。

どうして?基本的には、List<A>List<B>(またはList<string>List<int>は同じものです)は、完全に独立した2つの独立したクラスと見なされるためです。
stringを返すように宣言されている関数からintを返すことができないのと同様に、intのリストを返すように宣言されている関数から文字列のリストを返すことはできません。ここでの<T>は、ちょっとした赤いニシンです。文字列と整数の両方を返すジェネリックメソッドを作成できませんでした...

この種の詳細については、 こちら を参照してください。

したがって、あなたがしなければならないことは、両方の型から派生したものを返すことです(それらは「共通」しています)。
John Raschが言うようにIListを返すことができます(NONジェネリックに注意してください。つまり、objectsの単なるリストです)。 objectとして。残念ながら、リストのタイプを保存する方法はありません。

9
Orion Edwards

事前に実際の型を指定できない特別な理由がない限り、メソッド自体をジェネリックにすることができます。

public void Main() {
    List<A> a = GetData<A>();
}

public List<TType> GetData<TType>() {
     List<TType> list= new List<TType>();
     ...
     return list; 
}
7
Kevin Kibler

以下のオリオンの回答ごとに編集し、AnthonyWJonesが提案した制約を追加

あなたはおそらくAとBが継承しているインターフェイス/抽象クラスを持っているはずです

    public interface IMyInterface { }
    public class A : IMyInterface { }
    public class B : IMyInterface { }

    public List<IMyInterface> GetData<T>() where T : IMyInterface
    {
        List<IMyInterface> myList = new List<IMyInterface>();
        if (typeof(T) == typeof(A))
        {
            myList.Add(new A());
        }
        if (typeof(T) == typeof(B))
        {
            myList.Add(new B());
        }
        return myList;
    }
5
Jon Erickson

私は最近、同様の問題を解決しなければならず、提案された解決策のどれも満足のいくものではありませんでした。タイプパラメータの制約は実用的ではありませんでした。代わりに、私はメソッドのコンシューマにデータを変更する方法を決定させます。たとえば、部分文字列をTに変換する方法を指示する限り、厳密に型指定されたリストを返すString.Split()の汎用バージョンを作成できます。

責任をコールスタックに移すことを承諾した後(そしてラムダを快適に渡せるようになったら)、このパターンを任意に一般化できます。たとえば、GetData()の方法が変化する場合(一部の応答が明らかに想定しているように)、その関数を呼び出し元のスコープに引き上げることもできます。

デモ:

static void Main(string[] args)
{
    var parseMe = "Hello world!  1, 2, 3, DEADBEEF";

    // Don't need to write a fully generic Process() method just to parse strings -- you could 
    // combine the Split & Convert into one method and eliminate 2/3 of the type parameters
    List<string> sentences = parseMe.Split('!', str => str);
    List<int> numbers = sentences[1].Split(',', str => Int32.Parse(str, NumberStyles.AllowHexSpecifier | NumberStyles.AllowLeadingWhite));

    // Something a little more interesting
    var lettersPerSentence = Process(sentences,
                                     sList => from s in sList select s.ToCharArray(),
                                     chars => chars.Count(c => Char.IsLetter(c)));
}

static List<T> Split<T>(this string str, char separator, Func<string, T> Convert)
{       
    return Process(str, s => s.Split(separator), Convert).ToList();
}

static IEnumerable<TOutput> Process<TInput, TData, TOutput>(TInput input, Func<TInput, IEnumerable<TData>> GetData, Func<TData, TOutput> Convert)
{
    return from datum in GetData(input)
           select Convert(datum);
}

関数型プログラミングの第一人者は、おそらくこの探査で「あっという間にMapを作成するだけです」とあくびをするでしょう。 C++の開発者でさえ、テンプレートテクニック(つまり、STL transform()+ファンクタ)がジェネリックよりも少ない作業で済む例であると主張するかもしれません。しかし、主にC#を実行する人として、型の安全性と慣用的な言語の使用の両方を維持するソリューションを見つけるのは良かったです。

2
Richard Berg

あなたは次のようなことをすることができます:

public void Main()
{
    List<int> a = GetData<int>();
    List<string> b = GetData<string>();
}

public List<T> GetData<T>()
{
    var type = typeof(T);
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        type = type.GenericTypeArguments[0];
    }

    if (type == typeof(int))
    {
        var a = new List<int> { 1, 2, 3 };
        return a.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
    else if (type == typeof(string))
    {
        var b = new List<string> { "a", "b", "c" };
        return b.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
}

多分あなたはあなたのニーズにそれを変更することができます。

2
Marc Selman

実行時まで必要なタイプがわからない場合、ジェネリックはおそらくジョブに適したツールではありません。

関数が引数に基づいて動作を大幅に変更する場合(戻り値の型を変更する場合など)は、おそらく2つの関数でなければなりません。

この関数はジェネリックではなく、実際には2つの関数である必要があります。

public void Main() {
    List<A> a = GetDataA();
}

public List<A> GetDataA() {
     List<A> a= new List<A>() 
     ...
     return a; 
}
public List<B> GetDataB() {
     List<B> b= new List<B>() 
     ...
     return b; 
}
1
Craig Gidney

解決策は、 Visitor Patternclientとして機能するコンテナーにデータをカプセル化することです。

最初に、パターンに一致するいくつかのインターフェース:

_/// <summary>
/// The Client
/// </summary>
interface IDataContainer
{
    void AcceptDataProcessor(IDataProcessor dataProcessor);
}

/// <summary>
/// The Visitor.
/// </summary>
interface IDataProcessor
{
    void WorkOn<TData>(List<TData> data);
}
_

次に、それぞれの実装:

_class DataContainer<TData> : IDataContainer
{
    readonly List<TData> list;

    public DataContainer(List<TData> list)
    {
        this.list = list;
    }

    public void AcceptDataProcessor(IDataProcessor dataProcessor)
    {
        dataProcessor.WorkOn(list); // Here the type is known.
    }
}

class PrintDataProcessor : IDataProcessor
{
    public void WorkOn<TData>(List<TData> data)
    {
        // print typed data.
    }
}
_

次に、それを使用します:

_public void Main()
{
    var aContainer = GetData("A");
    var bContainer = GetData("B");

    var printProccessor = new PrintDataProcessor();

    aContainer.AcceptDataProcessor(printProccessor); // Will print A data
    bContainer.AcceptDataProcessor(printProccessor); // Will print B data
}


public IDataContainer GetData(string listType)
{
    if (listType == "A")
        return new DataContainer<A>(new List<A>());
    if (listType == "B")
        return new DataContainer<B>(new List<B>());
    throw new InvalidOperationException();
}
_

考えは、DataContainerは基礎となる型を知っていますが、それを公開しないということです。

  • 公開しないため、GetDataはあらゆる種類のデータを含むことができます(ただし、非表示になっています)。
  • DataContainer基礎となる型がわかっているため、ワーカーの適切な型付きメソッドを呼び出す必要があります:dataProcessor.WorkOn(list);

これは強力なパターンですが、コードの面で多くのコストがかかります。

0
Orace

私はその方法が遅すぎることを知っていますが、同じ問題を抱えてここに来ました。これが、インターフェイスを使用して解決する方法です。他の人のために投稿するつもりです

 public interface IEntity
    {
        int ID
        {
            get;
            set;
        }
    }

public class Entity2:IEntity
    {
        public string Property2;

        public int ID
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

Entity1についても同様です。

今私のクラス(私のビジネスレイヤー)でこのメソッドがあります

 public List<IEntity> GetEntities(Common.EntityType entityType)
           {
               List<IEntity> entities = new List<IEntity>();

               switch (entityType)
               {
                   case Common.EntityType.Accounts:
                       Entity1 entity1 = new Entity1();
                       entity1.Property1 = "AA";
                       entities.Add(entity1);

                       break;
                   case Common.EntityType.Brands:
                       Entity2 entity2 = new Entity2();
                       entity2.Property2 = "AA";
                       entities.Add(entity2);

                       break;
                   default:
                       break;
               }

 return entities;
       }

UIから、私はそれをこのように呼びます

BusinessClass b = new BusinessClass();
        List<IEntity> a = b.GetEntities(Common.EntityType.Accounts);

お役に立てれば

0
hangar18