C# Define a tuple as type
Asked Answered
L

3

5

Is there a way in C# to define a custom type which holds a tuple? e.g.:

Type MyParams Tuple<string, string> ?

Motive: I have the following function in a parent class:

protected virtual IEnumerable<(string, string)> GetConfigs()

that will be overridden by each child class. I'm looking for a way to define that tuple as type to declare the return type as IEnumerable<Params> instead of duplicating the tuple.

Lifeless answered 31/7, 2022 at 13:18 Comment(4)
How about just write a (record) struct? Is that what you want?Watereddown
POV: This class will be used through a nuget by others. So, I want to minimize how much they'd need to write for a new config. a struct would need to declare (new .... {Attribute = ... }), while with tuples they can just return ("a", "b").Lifeless
TBH value tuples are probably NOT a good type to expose in a library API. It is terrible documentation.Detent
@AhmadMasalha Note that you actually want a ValueTuple and not Tuple if you want to be able to use the ("a", "b") syntax.Submerge
T
11
  • Create a record, which is a reference (C# 9+) or value type (C# 10+) which is immutable and behaves like a value type in both cases. A C# record is either a class or a struct having functionality added automatically by the compiler. See also Records (C# reference) and Use record types - C# tutorial | Microsoft Learn.
  • Create a struct, which is a value type.
  • Or declare:
    global using MyParams = System.ValueTuple<string, string>;
    
    Starting with C#12 you can create aliases for any type, i.e., also for ValueTuples:
    global using MyParams = (string key , string value);
    

This global using must precede any other using in the file. Versions prior to C# 12 do not allow creating aliases with the C# tuple syntax (string a, string b). Therefore, you will have to access the tuple elements with t.Item1, t.Item2 etc.

If you omit the global keyword (C# 10+), the scope of the directive is limited to the current file.

Now you can declare the method as

protected virtual IEnumerable<MyParams> GetConfigs()
{
   ...
}

Note; however, that this global using is only known in the current assembly!

Therefore, its use in a public API of a class library is limited.

It is still possible to access members of this library using this type, so.

Having this declaration in one assembly

global using MyParams = System.ValueTuple<string, string>;

namespace MyLibrary;

public class Class1
{
    public MyParams p = ("hello", "world");
}

You can write this in another assembly referencing the first one:

var obj = new MyLibrary.Class1();
Console.WriteLine(obj.p);

But a declaration like MyLibrary.MyParams p; is not possible in the other assembly.

Tenotomy answered 31/7, 2022 at 13:29 Comment(0)
A
6

What you're looking for is called alias using directive. However, according to the documentation you can define it with global scope starting from C# 10. Otherwise, you need to make an alias in every file when you want to use it.

using MyParams = System.Tuple<string, string>;

usage:

var foo = new MyParams("foo", "baz");
Anglicism answered 31/7, 2022 at 13:32 Comment(0)
F
4

Firstly, I would definitely recommend having a proper defined type given it's for an API (although in fairness I have a dislike for tuples anyway). Something like this might do, with type conversions to/from a (string, string):

public struct SuitableTypeName
{
    public string SomeName;
    public string SomeDifferentName;

    public SuitableTypeName(string someName, string someDifferentName)
    {
        SomeName = someName;
        SomeDifferentName = someDifferentName;
    }

    public static implicit operator (string, string)(SuitableTypeName name)
    {
        return (name.SomeName, name.SomeDifferentName);
    }

    public static explicit operator SuitableTypeName ((string, string) tuple)
    {
        return new SuitableTypeName(tuple.Item1, tuple.Item2);
    }
}

Note that depending you might consider using a class rather than a struct. In this example it's a mutable struct which may lead to confusion/unexpected behavior, so if you're worried about that you could also make it immutable. Also, some groups have very strong feelings about mutable structs for some reason, so if you're working in a group and decide to use it as-is you may want to check your coding standards or clear it with the others.

Feldt answered 31/7, 2022 at 13:41 Comment(3)
I really like this idea using type conversion!Lifeless
As mentioned by others, records may also be a suitable option. Although depending what your particular needs are, you may end up overriding == anyway, or it might not make any real difference.Feldt
You could add a Deconstructor.Tenotomy

© 2022 - 2024 — McMap. All rights reserved.