タプルクラスでの「Item1」、「Item2」よりも優れた命名


204

タプルクラスを使用する方法はありますが、その中のアイテムの名前を提供しますか?

例えば:

public Tuple<int, int, int int> GetOrderRelatedIds()

これにより、OrderGroupId、OrderTypeId、OrderSubTypeId、OrderRequirementIdのIDが返されます。

私のメソッドのユーザーにどちらがどれであるかを知らせておくとよいでしょう。(メソッドを呼び出すと、結果はresult.Item1、result.Item2、result.Item3、result.Item4になります。どちらがどれかは明確ではありません。)

(私はこれらすべてのIDを保持するクラスを作成できることを知っていますが、これらのIDにはすでに独自のクラスがあり、この1つのメソッドの戻り値のクラスを作成するのはばかげているように見えます。)


1
あなたは自分でロールする必要があります- Tuple非常に一般的です、それであなたが得るすべてです
BrokenGlass

いいえ、そのようにすることはできません。詳細については、このリンクを参照してください。msdn.microsoft.com
Enigma State

1
タプルをAPIの公開データ型として使用することはお勧めできないかもしれません。私は通常、APIの戻り値としてではなく、有効期間の短い内部的なものにタプルを使用します。
Mike Burdick、2015年


4
それはC#7の作業リストにありますgithub.com/dotnet/roslyn/issues/347を
フィリップディン

回答:


277

C#7.0(Visual Studio 2017)では、これを行うための新しい構造があります。

(string first, string middle, string last) LookupName(long id)

68
構文はList<(int first, int second)>です。私は、Visual Studio 2017で動作するようにそれを得るためにNuGetからSystem.ValueTupleパッケージをダウンロードする必要がありました
マット・デイビス

14
価値を生み出すreturn (first: first, middle: middle, last: last);
フィアット2017

4
またはのみ:return (first, middle, last);.NET 4.7.1(4.7.0では不明)
watbywbarif '13

1
これを使用するには、System.ValueTuple nugetパッケージを追加する必要があります
Alex G

11
C#7 ValueTupleは、通常は素晴らしいですが、変更可能な値の型(構造体)でTupleあり、不変の参照型(クラス)であることに注意してください。私の知る限り、Tupleわかりやすいアイテム名を持つ参照型を取得する方法はありません。
dx_over_dt

51

C#7.0までは、独自の型を定義する以外にこれを行う方法はありませんでした。


13
この答えが40のスコアで受け入れられるとは信じられません。少なくとも、適切なコンストラクターを持つクラスがこれをどのように置き換えることができるかを示すことができます。
bytecode77

1
@ bytecode77さて、まもなくこの答えは間違いになるでしょう:github.com/dotnet/roslyn/issues/347
MarkPflug

私はこれらの言語フェスチャーの提案を見てきました。しかし、これまでは、クラスはより複雑なデータ型に対する唯一の適切なソリューションです。なぜ、タプルを強制的に使用したいのですか(他の回答を参照)
bytecode77

3
C#7がリリースされた後、これを実行できるようになります:msdn.microsoft.com/en-us/magazine/mt595758.aspx
BurakKarakuşSep

11
Q 'はタグとしてc#4を持っているので、この答えは短いですが、それでも正しいです。
Steve Drake

33

ここにあなたが求めているものの過度に複雑なバージョンがあります:

class MyTuple : Tuple<int, int>
{
    public MyTuple(int one, int two)
        :base(one, two)
    {

    }

    public int OrderGroupId { get{ return this.Item1; } }
    public int OrderTypeId { get{ return this.Item2; } }

}

クラスを作ってみませんか?


2
この場合、クラスの代わりに構造体の方が優れていますか?
デスレース

5
これのわずかな利点は、equals演算子が自動的に実装され、項目がすべて等しい場合に2つのインスタンスが等しいことを確認することです。
JSoet

8
このアプローチのもう1つの欠点は、Item1とItem2がまだMyTupleのパブリックプロパティであることです
RJFalconer

3
@deathraceタプル自体はクラスであるため、直接継承したい場合Tuple<T, T2>は構造体にすることはできません。
チャクラバ

3
..私は間違っているかもしれないが、私は、オブジェクトを返すようにしたいが、特定のクラスを定義する必要はありませんどこに私は主にタプルを使う
ジェイ

12

.net 4をExpandoObject使用すると、おそらくを見ることができますが、コンパイル時のエラーがランタイムエラーになるため、この単純なケースでは使用しないでください。

class Program
{
    static void Main(string[] args)
    {
        dynamic employee, manager;

        employee = new ExpandoObject();
        employee.Name = "John Smith";
        employee.Age = 33;

        manager = new ExpandoObject();
        manager.Name = "Allison Brown";
        manager.Age = 42;
        manager.TeamSize = 10;

        WritePerson(manager);
        WritePerson(employee);
    }
    private static void WritePerson(dynamic person)
    {
        Console.WriteLine("{0} is {1} years old.",
                          person.Name, person.Age);
        // The following statement causes an exception
        // if you pass the employee object.
        // Console.WriteLine("Manages {0} people", person.TeamSize);
    }
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.

他に言及する価値のあるものは、メソッド内の匿名型 ですが、それを返すには、クラスを作成する必要があります。

var MyStuff = new
    {
        PropertyName1 = 10,
        PropertyName2 = "string data",
        PropertyName3 = new ComplexType()
    };

10

この投稿からの私の回答を再現します。

C#v7.0以降、以前はデフォルトでのような定義済みの名前にデフォルトで使用されていたタプルプロパティに名前を付けることができるようItem1Item2なりました。

タプルリテラルのプロパティの名前付け

var myDetails = (MyName: "RBT_Yoga", MyAge: 22, MyFavoriteFood: "Dosa");
Console.WriteLine($"Name - {myDetails.MyName}, Age - {myDetails.MyAge}, Passion - {myDetails.MyFavoriteFood}");

コンソールの出力:

名前-RBT_Yoga、年齢-22、情熱-Dosa

メソッドからタプル(名前付きプロパティを持つ)を返す

static void Main(string[] args)
{
    var empInfo = GetEmpInfo();
    Console.WriteLine($"Employee Details: {empInfo.firstName}, {empInfo.lastName}, {empInfo.computerName}, {empInfo.Salary}");
}

static (string firstName, string lastName, string computerName, int Salary) GetEmpInfo()
{
    //This is hardcoded just for the demonstration. Ideally this data might be coming from some DB or web service call
    return ("Rasik", "Bihari", "Rasik-PC", 1000);
}

コンソールの出力:

従業員の詳細:Rasik、Bihari、Rasik-PC、1000

名前付きプロパティを持つタプルのリストを作成する

var tupleList = new List<(int Index, string Name)>
{
    (1, "cow"),
    (5, "chickens"),
    (1, "airplane")
};

foreach (var tuple in tupleList)
    Console.WriteLine($"{tuple.Index} - {tuple.Name}");

コンソールでの出力:

1-牛5-鶏1-飛行機

私はすべてをカバーしたと思います。見逃してしまったことがありましたら、コメント欄でフィードバックをお願いします。

ここで説明するように、私のコードスニペットはC#v7の文字列補間機能を使用しています。


3

MichaelMocko Answeredは素晴らしい

しかし、私は理解しなければならなかったいくつかのことを追加したいです

(string first, string middle, string last) LookupName(long id)

上記の行では、.netフレームワーク<4.7を使用している場合、コンパイル時にエラーが発生します。

.netフレームワーク<4.7を使用しているプロジェクトがあり、それでも、workAroundがこの nugetパッケージをインストールするよりもValueTupleを使用したい場合



2

アイテムの種類がすべて異なる場合は、より直感的に取得できるようにクラスを作成しました。

このクラスの使用法:

var t = TypedTuple.Create("hello", 1, new MyClass());
var s = t.Get<string>();
var i = t.Get<int>();
var c = t.Get<MyClass>();

ソースコード:

public static class TypedTuple
{
    public static TypedTuple<T1> Create<T1>(T1 t1)
    {
        return new TypedTuple<T1>(t1);
    }

    public static TypedTuple<T1, T2> Create<T1, T2>(T1 t1, T2 t2)
    {
        return new TypedTuple<T1, T2>(t1, t2);
    }

    public static TypedTuple<T1, T2, T3> Create<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new TypedTuple<T1, T2, T3>(t1, t2, t3);
    }

    public static TypedTuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4)
    {
        return new TypedTuple<T1, T2, T3, T4>(t1, t2, t3, t4);
    }

    public static TypedTuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
    {
        return new TypedTuple<T1, T2, T3, T4, T5>(t1, t2, t3, t4, t5);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6>(t1, t2, t3, t4, t5, t6);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7>(t1, t2, t3, t4, t5, t6, t7);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8>(t1, t2, t3, t4, t5, t6, t7, t8);
    }

}

public class TypedTuple<T>
{
    protected Dictionary<Type, object> items = new Dictionary<Type, object>();

    public TypedTuple(T item1)
    {
        Item1 = item1;
    }

    public TSource Get<TSource>()
    {
        object value;
        if (this.items.TryGetValue(typeof(TSource), out value))
        {
            return (TSource)value;
        }
        else
            return default(TSource);
    }

    private T item1;
    public T Item1 { get { return this.item1; } set { this.item1 = value; this.items[typeof(T)] = value; } }
}

public class TypedTuple<T1, T2> : TypedTuple<T1>
{
    public TypedTuple(T1 item1, T2 item2)
        : base(item1)
    {
        Item2 = item2;
    }

    private T2 item2;
    public T2 Item2 { get { return this.item2; } set { this.item2 = value; this.items[typeof(T2)] = value; } }
}

public class TypedTuple<T1, T2, T3> : TypedTuple<T1, T2>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3)
        : base(item1, item2)
    {
        Item3 = item3;
    }

    private T3 item3;
    public T3 Item3 { get { return this.item3; } set { this.item3 = value; this.items[typeof(T3)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4> : TypedTuple<T1, T2, T3>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4)
        : base(item1, item2, item3)
    {
        Item4 = item4;
    }

    private T4 item4;
    public T4 Item4 { get { return this.item4; } set { this.item4 = value; this.items[typeof(T4)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5> : TypedTuple<T1, T2, T3, T4>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
        : base(item1, item2, item3, item4)
    {
        Item5 = item5;
    }

    private T5 item5;
    public T5 Item5 { get { return this.item5; } set { this.item5 = value; this.items[typeof(T5)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6> : TypedTuple<T1, T2, T3, T4, T5>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
        : base(item1, item2, item3, item4, item5)
    {
        Item6 = item6;
    }

    private T6 item6;
    public T6 Item6 { get { return this.item6; } set { this.item6 = value; this.items[typeof(T6)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7> : TypedTuple<T1, T2, T3, T4, T5, T6>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
        : base(item1, item2, item3, item4, item5, item6)
    {
        Item7 = item7;
    }

    private T7 item7;
    public T7 Item7 { get { return this.item7; } set { this.item7 = value; this.items[typeof(T7)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> : TypedTuple<T1, T2, T3, T4, T5, T6, T7>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
        : base(item1, item2, item3, item4, item5, item6, item7)
    {
        Item8 = item8;
    }

    private T8 item8;
    public T8 Item8 { get { return this.item8; } set { this.item8 = value; this.items[typeof(T8)] = value; } }
}

4
これは、ほとんどまたはまったく見返りがないため、大変な作業のようです。これには直感的でない制限(重複するタイプはありません)があり、そのタイプのみで値を取得するという考えは信じられないほど直感的ではなく、実際的な使用例を考えることはできません。これは、従業員用のデータテーブルを作成し、(一意のキーではなく)名で従業員を取得することを決定し、その後、すべての従業員に異なる名を要求することと同じです。これは問題の解決策ではなく、追加の問題を作成する代わりに解決策を使用しています。
2018年

そして、神があなたの魂を憐れんでくださいますように。
ジェイミーM.

1

これは非常に迷惑であり、C#の将来のバージョンではこのニーズに対応する予定です。最も簡単な回避策は、別のデータ構造タイプを使用するか、「アイテム」の名前をあなたの正気性とコードを読み取る他の人の正気性のために変更することです。

Tuple<ApiResource, JSendResponseStatus> result = await SendApiRequest();
ApiResource apiResource = result.Item1;
JSendResponseStatus jSendStatus = result.Item2;

0

クラスを作成すると思いますが、別の方法は出力パラメーターです。

public void GetOrderRelatedIds(out int OrderGroupId, out int OrderTypeId, out int OrderSubTypeId, out int OrderRequirementId)

タプルには整数しか含まれていないので、 Dictionary<string,int>

var orderIds = new Dictionary<string, int> {
    {"OrderGroupId", 1},
    {"OrderTypeId", 2},
    {"OrderSubTypeId", 3},
    {"OrderRequirementId", 4}.
};

でもお勧めしません。


0

なぜみんながそんなに大変なのか。タプルは、一時的なデータ処理用です。常にタプルを操作すると、コードがいつか理解しにくくなります。すべてのクラスを作成する、プロジェクトが肥大化する可能性があります。

バランスについてですが...

あなたの問題は、あなたがクラスを望んでいるもののようです。また、完全を期すために、以下のこのクラスにはコンストラクタも含まれています。


これは適切なパターンです

  • カスタムデータ型
    • それ以上の機能はありません。ゲッターとセッターはコードで拡張することもでき、「_ orderGroupId」の名前パターンを持つプライベートメンバーを取得/設定すると同時に、機能コードを実行します。
  • コンストラクタを含みます。すべての場合 1つのコンストラクタのみを含めることもできますプロパティが必須の。
  • すべてのコンストラクターを使用する場合は、このようなバブリングがコードの重複を避けるための適切なパターンです。

public class OrderRelatedIds
{
    public int OrderGroupId { get; set; }
    public int OrderTypeId { get; set; }
    public int OrderSubTypeId { get; set; }
    public int OrderRequirementId { get; set; }

    public OrderRelatedIds()
    {
    }
    public OrderRelatedIds(int orderGroupId)
        : this()
    {
        OrderGroupId = orderGroupId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId)
        : this(orderGroupId)
    {
        OrderTypeId = orderTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId)
        : this(orderGroupId, orderTypeId)
    {
        OrderSubTypeId = orderSubTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId, int orderRequirementId)
        : this(orderGroupId, orderTypeId, orderSubTypeId)
    {
        OrderRequirementId = orderRequirementId;
    }
}

または、本当にシンプルにしたい場合:タイプ初期化子を使用することもできます。

OrderRelatedIds orders = new OrderRelatedIds
{
    OrderGroupId = 1,
    OrderTypeId = 2,
    OrderSubTypeId = 3,
    OrderRequirementId = 4
};

public class OrderRelatedIds
{
    public int OrderGroupId;
    public int OrderTypeId;
    public int OrderSubTypeId;
    public int OrderRequirementId;
}

0

summayで項目名を記述します。したがって、関数helloworld()にカーソルを合わせると、テキストにhello = Item1およびworld = Item2と表示されます。

 helloworld("Hi1,Hi2");

/// <summary>
/// Return hello = Item1 and world Item2
/// </summary>
/// <param name="input">string to split</param>
/// <returns></returns>
private static Tuple<bool, bool> helloworld(string input)
{
    bool hello = false;
    bool world = false;
    foreach (var hw in input.Split(','))
    {
        switch (hw)
        {
            case "Hi1":
                hello= true;
                break;
            case "Hi2":
                world= true;
                break;
        }

    }
    return new Tuple<bool, bool>(hello, world);
}

0

@MichaelMockoの回答に追加するだけです。タプルには現時点でいくつかの落とし穴があります。

EF式ツリーでは使用できません

例:

public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        // Selecting as Tuple
        .Select(person => (person.Name, person.Surname))
        .First();
}

これは、「式ツリーにタプルリテラルが含まれていない可能性があります」というエラーでコンパイルに失敗します。残念ながら、式ツリーAPIは、タプルが言語に追加されたときにタプルをサポートするように拡張されていません。

アップデートのためにこの問題を追跡(および賛成):https : //github.com/dotnet/roslyn/issues/12897

問題を回避するには、まず匿名型にキャストしてから、値をタプルに変換します。

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => new { person.Name, person.Surname })
        .ToList()
        .Select(person => (person.Name, person.Surname))
        .First();
}

別のオプションは、ValueTuple.Createを使用することです。

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname))
        .First();
}

参照:

ラムダで分解することはできません

サポートを追加する提案がありますhttps : //github.com/dotnet/csharplang/issues/258

例:

public static IQueryable<(string name, string surname)> GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname));
}

// This won't work
ctx.GetPersonName(id).Select((name, surname) => { return name + surname; })

// But this will
ctx.GetPersonName(id).Select(t => { return t.name + t.surname; })

参照:

彼らはうまくシリアライズされません

using System;
using Newtonsoft.Json;

public class Program
{
    public static void Main() {
        var me = (age: 21, favoriteFood: "Custard");
        string json = JsonConvert.SerializeObject(me);

        // Will output {"Item1":21,"Item2":"Custard"}
        Console.WriteLine(json); 
    }
}

タプルフィールド名はコンパイル時にのみ使用でき、実行時に完全に消去されます。

参照:


-1

タプルを含むクラスを作成できます。

Equals関数とGetHashCode関数をオーバーライドする必要がある

および==および!=演算子。

class Program
{
    public class MyTuple
    {
        private Tuple<int, int> t;

        public MyTuple(int a, int b)
        {
            t = new Tuple<int, int>(a, b);
        }

        public int A
        {
            get
            {
                return t.Item1;
            }
        }

        public int B
        {
            get
            {
                return t.Item2;
            }
        }

        public override bool Equals(object obj)
        {
            return t.Equals(((MyTuple)obj).t);
        }

        public override int GetHashCode()
        {
            return t.GetHashCode();
        }

        public static bool operator ==(MyTuple m1, MyTuple m2)
        {
            return m1.Equals(m2);
        }

        public static bool operator !=(MyTuple m1, MyTuple m2)
        {
            return !m1.Equals(m2);
        }
    }

    static void Main(string[] args)
    {
        var v1 = new MyTuple(1, 2);
        var v2 = new MyTuple(1, 2);

        Console.WriteLine(v1 == v2);

        Dictionary<MyTuple, int> d = new Dictionary<MyTuple, int>();
        d.Add(v1, 1);

        Console.WriteLine(d.ContainsKey(v2));
    }
}

戻ります:

本当

本当


2
このデータ型のクラスをすでに実装している場合、プロパティだけでなく、基になるデータのタプルを宣言するのはなぜですか?
bytecode77

Equals関数の値によって比較されるタプル属性を使用したい
ss

それはボーナスかもしれません。しかし一方で、基本的には、Item1からItemXまでの範囲のプロパティを持つクラスを作成しました。Equals()でタプルを使用するよりも適切な名前とより多くのコードを選択します。
bytecode77

-1

C#7タプルの例

var tuple = TupleExample(key, value);

     private (string key1, long value1) ValidateAPIKeyOwnerId(string key, string value)
            {
                return (key, value);
            }
      if (!string.IsNullOrEmpty(tuple.key1) && tuple.value1 > 0)
          {
                    //your code

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