development

C # 코드에서 .NET 4.0 튜플을 사용하는 것이 좋지 않은 디자인 결정입니까?

big-blog 2020. 5. 28. 19:14
반응형

C # 코드에서 .NET 4.0 튜플을 사용하는 것이 좋지 않은 디자인 결정입니까?


.net 4에 Tuple 클래스 가 추가되면서 디자인에 사용하는 것이 나쁜 선택인지 여부를 결정하려고했습니다. 내가 보는 방식으로 Tuple 은 결과 클래스를 작성하는 지름길이 될 수 있습니다 (다른 용도도 있음).

그래서 이건:

public class ResultType
{
    public string StringValue { get; set; }
    public int IntValue { get; set; }
}

public ResultType GetAClassedValue()
{
    //..Do Some Stuff
    ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
    return result;
}

이것과 동일합니다 :

public Tuple<string, int> GetATupledValue()
{
    //...Do Some stuff
    Tuple<string, int> result = new Tuple<string, int>("A String", 2);
    return result;
}

옆으로 난 튜플의 지점을 잃었 가능성을 설정하며,이와 예입니다 튜플은 나쁜 디자인 선택? 나에게 그것은 덜 어수선 해 보이지만 자기 문서화와 깨끗함은 아닙니다. type ResultType을 사용하면 클래스의 각 부분이 무엇을 의미하는지 명확하게 알 수 있지만 유지 관리해야 할 추가 코드가 있습니다. 를 사용하면 Tuple<string, int>각각이 Item나타내는 내용을 찾아서 파악해야 하지만 코드를 적게 작성하고 유지 관리해야합니다.

이 선택에 대한 경험이 있으면 대단히 감사하겠습니다.


튜플은 생성 및 사용을 모두 제어 할 경우 유용합니다. 컨텍스트를 유지 관리 할 수 ​​있으므로 이해하는 데 필수적입니다.

그러나 공용 API에서는 효과가 떨어집니다. 소비자 (귀하가 아닌)는 문서를 추측하거나 조회해야합니다 Tuple<int, int>.

개인 / 내부 회원에게는 사용하지만 공개 / 보호 회원에게는 결과 클래스를 사용합니다.

이 답변 에는 정보가 있습니다.


내가 보는 방식으로 Tuple은 결과 클래스를 작성하는 지름길입니다 (다른 용도도 있음).

실제로 다른 귀중한 용도가 있습니다Tuple<> . 대부분 유사한 구조를 공유하는 특정 유형의 그룹의 의미를 추상화하고 단순히 일련의 값으로 취급합니다. 모든 경우에 튜플의 이점은 속성은 노출하지만 메서드는 노출하지 않는 데이터 전용 클래스로 네임 스페이스를 어지럽히 지 않는다는 것입니다.

다음은 합리적으로 사용되는 예입니다 Tuple<>.

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

위 예제에서 우리는 한 쌍의 상대를 나타내려고합니다. 튜플은 새 클래스를 만들지 않고도 이러한 인스턴스를 연결하는 편리한 방법입니다. 또 다른 예는 다음과 같습니다.

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

포커 패는 단순한 카드 세트로 생각할 수 있으며 튜플은 그 개념을 표현하는 합리적인 방법입니다.

Tuples의 요점을 놓칠 가능성을 제쳐두고 Tuple이있는 예는 나쁜 디자인 선택입니까?

Tuple<>퍼블릭 타입의 퍼블릭 API의 일부로 강력한 타입의 인스턴스를 반환하는 것은 좋은 생각이 아닙니다. 본인이 알고 있듯이 튜플을 사용하려면 관련 당사자 (라이브러리 작성자, 라이브러리 사용자)가 사용중인 튜플 유형의 목적과 해석에 미리 동의해야합니다. Tuple<>공개적으로 API의 의도와 행동을 모호하게하는 것만으로 직관적이고 명확한 API를 만드는 것은 어려운 일 입니다.

익명 형식도 일종의 튜플 이지만 강력하게 형식이 지정되어 있으며 해당 형식에 속하는 속성에 대해 명확하고 유익한 이름을 지정할 수 있습니다. 그러나 익명 유형은 다른 방법으로 사용하기가 어렵습니다. 주로 LINQ와 같은 기술을 지원하기 위해 추가되었는데, 여기서는 투영에서 일반적으로 이름을 지정하고 싶지 않은 유형이 생성됩니다. (예, 동일한 유형과 명명 된 속성을 가진 익명 유형이 컴파일러에 의해 통합되어 있음을 알고 있습니다).

내 경험 법칙은 공용 인터페이스에서 반환하는 경우 명명 된 유형으로 만드십시오 .

튜플을 사용하는 또 다른 경험의 규칙은 다음 같습니다. 이름 메서드 인수와 유형의 localc 변수 Tuple<>를 가능한 명확하게-이름이 튜플 요소 간의 관계의 의미를 나타내도록합니다. var opponents = ...예를 생각해보십시오 .

다음 은 내 어셈블리 내에서만 사용 Tuple<>하기 위해 데이터 전용 유형 선언하지 않기 위해 사용한 실제 사례의 예입니다 . 익명 유형을 포함하는 일반 사전을 사용하는 경우 TryGetValue()메소드에 out이름을 지정할 수없는 매개 변수가 필요하기 때문에 메소드를 사용 하여 사전에서 항목을 찾는 것이 어려워지는 상황이 있습니다 .

public static class DictionaryExt 
{
    // helper method that allows compiler to provide type inference
    // when attempting to locate optionally existent items in a dictionary
    public static Tuple<TValue,bool> Find<TKey,TValue>( 
        this IDictionary<TKey,TValue> dict, TKey keyToFind ) 
    {
        TValue foundValue = default(TValue);
        bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
        return Tuple.Create( foundValue, wasFound );
    }
}

public class Program
{
    public static void Main()
    {
        var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
                             new { LastName = "Sanders", FirstName = "Bob" } };

        var peopleDict = people.ToDictionary( d => d.LastName );

        // ??? foundItem <= what type would you put here?
        // peopleDict.TryGetValue( "Smith", out ??? );

        // so instead, we use our Find() extension:
        var result = peopleDict.Find( "Smith" );
        if( result.First )
        {
            Console.WriteLine( result.Second );
        }
    }
}

P.S. There is another (simpler) way of getting around the issues arising from anonymous types in dictionaries, and that is to use the var keyword to let the compiler 'infer' the type for you. Here's that version:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}

Tuples can be useful... but they can also be a pain later. If you have a method that returns Tuple<int,string,string,int> how do you know what those values are later. Were they ID, FirstName, LastName, Age or were they UnitNumber, Street, City, ZipCode.


Tuples are pretty underwhelming addition to the CLR from the perspective of a C# programmer. If you have a collection of items that varies in length, you don't need them to have unique static names at compile time.

But if you have a collection of constant length, this implies that the fixed of locations in the collection each have a specific pre-defined meaning. And it is always better to give them appropriate static names in that case, rather than having to remember the significance of Item1, Item2, etc.

Anonymous classes in C# already provide a superb solution to the most common private use of tuples, and they give meaningful names to the items, so they are actually superior in that sense. The only problem is that they can't leak out of named methods. I'd prefer to see that restriction lifted (perhaps only for private methods) than have specific support for tuples in C#:

private var GetDesserts()
{
    return _icecreams.Select(
        i => new { icecream = i, topping = new Topping(i) }
    );
}

public void Eat()
{
    foreach (var dessert in GetDesserts())
    {
        dessert.icecream.AddTopping(dessert.topping);
        dessert.Eat();
    }
}

Similar to keyword var, it is intended as a convenience - but is as easily abused.

In my most humble opinion, do not expose Tuple as a return class. Use it privately, if a service or component's data structure requires it, but return well-formed well-known classes from public methods.

// one possible use of tuple within a private context. would never
// return an opaque non-descript instance as a result, but useful
// when scope is known [ie private] and implementation intimacy is
// expected
public class WorkflowHost
{
    // a map of uri's to a workflow service definition 
    // and workflow service instance. By convention, first
    // element of tuple is definition, second element is
    // instance
    private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = 
        new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
}

Using a class like ResultType is clearer. You can give meaningful names to the fields in the class (whereas with a tuple they would be called Item1 and Item2). This is even more important if the types of the two fields are the same: the name clearly distinguishes between them.


How about using Tuples in a decorate-sort-undecorate pattern? (Schwartzian Transform for the Perl people). Here's a contrived example, to be sure, but Tuples seem to be a good way to handle this kind of thing:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] files = Directory.GetFiles("C:\\Windows")
                    .Select(x => new Tuple<string, string>(x, FirstLine(x)))
                    .OrderBy(x => x.Item2)
                    .Select(x => x.Item1).ToArray();
        }
        static string FirstLine(string path)
        {
            using (TextReader tr = new StreamReader(
                        File.Open(path, FileMode.Open)))
            {
                return tr.ReadLine();
            }
        }
    }
}

Now, I could have used an Object[] of two elements or in this specific example a string [] of two elements. The point being that I could have used anything as the second element in a Tuple that's used internally and is pretty easy to read.


IMO these "tuples" are basically all public access anonymous struct types with unnamed members.

The only place I would use tuple is when you need to quickly blob together some data, in a very limited scope. The semantics of the data should be are obvious, so the code is not hard to read. So using a tuple (int,int) for (row,col) seems reasonable. But I'm hard pressed to find an advantage over a struct with named members (so no mistakes are made and row/column aren't accidentally interchanged)

If you're sending data back to the caller, or accepting data from a caller, you really should be using a struct with named members.

Take a simple example:

struct Color{ float r,g,b,a ; }
public void setColor( Color color )
{
}

The tuple version

public void setColor( Tuple<float,float,float,float> color )
{
  // why?
}

I don't see any advantage to using tuple in the place of a struct with named members. Using unnamed members is a step backward for the readability and understandability of your code.

Tuple strikes me as a lazy way to avoid creating a struct with actual named members. Overuse of tuple, where you really feel you/or someone else encountering your code would need named members is A Bad Thing™ if I ever saw one.


Don't judge me, I'm not an expert, but with new Tuples in C# 7.x now, you could return something like:

return (string Name1, int Name2)

At least now you can name it and developers might see some information.


It depends, of course! As you said, a tuple can save you code and time when you want to group some items together for local consumption. You can also use them to create more generic processing algorithms than you can if you pass a concrete class around. I can't remember how many times I've wished I had something beyond KeyValuePair or a DataRow to quickly pass some date from one method to another.

On the other hand, it is quite possible to overdo it and pass around tuples where you can only guess what they contain. If you are going to use a tuple across classes, perhaps it would be better to create one concrete class.

Used in moderation of course, tuples can lead to more concise and readable code. You can look to C++, STL and Boost for examples of how Tuples are used in other languages but in the end, we will all have to experiment to find how they best fit in the .NET environment.


Tuples are a useless framework feature in .NET 4. I think a great opportunity was missed with C# 4.0. I would have loved to have tuples with named members, so you could access the various fields of a tuple by name instead of Value1, Value2, etc...

It would have required a language (syntax) change, but it would have been very useful.


I would personally never use a Tuple as a return type because there is no indication of what the values represent. Tuples have some valuable uses because unlike objects they are value types and thus understand equality. Because of this I will use them as dictionary keys if I need a multipart key or as a key for a GroupBy clause if I want to group by multiple variables and don't want nested groupings (Who ever wants nested groupings?). To overcome the issue with extreme verbosity you can create them with a helper method. Keep in mind if you are frequently accessing members (through Item1, Item2, etc) then you should probably use a different construct such as a struct or an anonymous class.


I've used tuples, both the Tuple and the new ValueTuple, in a number of different scenarios and arrived at the following conclusion: do not use.

Every time, I encountered the following issues:

  • code became unreadable due to lack of strong naming;
  • unable to use class hierarchy features, such as base class DTO and child class DTOs, etc.;
  • if they are used in more than one place, you end up copying and pasting these ugly definitions, instead of a clean class name.

My opinion is tuples are a detriment, not a feature, of C#.

I have somewhat similar, but a lot less harsh, criticism of Func<> and Action<>. Those are useful in many situations, especially the simple Action and Func<type> variants, but anything beyond that, I've found that creating a delegate type is more elegant, readable, maintainable, and gives you more features, like ref/out parameters.

참고URL : https://stackoverflow.com/questions/3017352/is-using-net-4-0-tuples-in-my-c-sharp-code-a-poor-design-decision

반응형