Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tuple types in using directives #423

Closed
gafter opened this issue Apr 10, 2017 · 65 comments
Closed

Tuple types in using directives #423

gafter opened this issue Apr 10, 2017 · 65 comments

Comments

@gafter
Copy link
Member

gafter commented Apr 10, 2017

@gafter commented on Mon Nov 07 2016

It has been requested that we support

using Subject = (bool Gender, double Age);

@HaloFour commented on Mon Nov 07 2016

🍝 Generic version when combined with #3993?

using Subject<T> = (T Gender, double Age);

@eyalsk commented on Mon Nov 07 2016

That's pretty cool!


@DavidArno commented on Wed Nov 09 2016

At first glance, this seems an excellent idea. But then it takes on the texture of "aerosol cream": it looks like rich, thick, real cream but turns out to be 90% air. Yes, it would be useful occasionally, but the scope would then just be within that file. Some means of giving it a wider scope would really make this useful, eg:

Subject.cs

public global Subject<T> = (T Gender, double Age);

OtherFile.cs

bool GetGender(Subject<bool> subject) => subject.Gender; 

var gender = GetGender((false, 10));

Update
Having thought though this topic, I now feel that @alrz's point re records is pertinent. There should be no need for tuple aliases when we have records. So I no longer think this a useful feature at all.


@dsaf commented on Tue Nov 08 2016

@DavidArno this was previously rejected for some reason #7451.


@DavidArno commented on Tue Nov 08 2016

@dsaf,
That's a shame, but thanks for the link.


@MgSam commented on Tue Nov 08 2016

I'm happy with the proposal as @gafter specified. I think generics and global scope are both orthogonal to this feature and should be considered separately.


@DavidArno commented on Tue Nov 08 2016

Generics and global scope are enhancements to the basic proposal. So you appear to be misusing the term "orthogonal" 😛


@MgSam commented on Tue Nov 08 2016

@DavidArno They are completely separable proposals that do not impact the merits of this feature as proposed. I'd call that orthogonal. Scope creep is the death of feature requests.


@qrli commented on Tue Nov 08 2016

I don't see this necessary. It is at best low priority.

If you want a type, define a type.


@alrz commented on Tue Nov 08 2016

Once we have records, it'll be a piece of cake.

struct Subject(bool Gender, double Age);

I'd personally prefer to have generic and global type aliases over this.


@orthoxerox commented on Wed Nov 09 2016

@DavidArno records do not cover all the use cases you might need aliases for. See my example in #7453, there's no way a record can replace a long-winded generic interface type declaration.


@DavidArno commented on Wed Nov 09 2016

@orthoxerox,

Apologies, my comment wasn't clear. I was referring to this specific feature request: tuple aliases, not type aliases in general. I've updated my comment to fix that.


@alrz commented on Wed Nov 09 2016

@DavidArno I'm saying that when you want a named tuple you'd better to define a struct record. But generic and global type aliases are useful for other scenarios, regardless of this proposal.


@AdamSpeight2008 commented on Wed Nov 09 2016

Will the diposable form of using also be supported.

using ( ( MyDisposable0, MyDisposable1 ) )
[
  ...
}

@alrz commented on Sat Nov 12 2016

@AdamSpeight2008 This is already supported:

using (IDisposable d1 = e1, d2 = e2) {}

Also see #9882 and #11420.


@AdamSpeight2008 commented on Sat Nov 12 2016

@alrz That's not what I'm asking.
Let's we have this function.

Function Foo(Of T As IDisposable(Of T), U As IDisposable(Of U)) () As ( T, U )
End Function

And it is call within the header of a using block.

using( Foo() ) { ... }
 // Will both part of the Tuple<T,U> be disposed?

Do the Tuple<T,U> dispose correctly the disposable items within the tuple? eg .Item1 , .Item2


@HaloFour commented on Sat Nov 12 2016

@AdamSpeight2008

#9882


@AdamSpeight2008 commented on Sat Nov 12 2016

@HaloFour If the tuple is decomposed into separate variables, we risk the chance of disposing of a copy.


@HaloFour commented on Sat Nov 12 2016

@AdamSpeight2008

That proposal goes over several potential implementations. Either way, this proposal is about using for aliases, not using for disposables. #9882 is about using for disposables and how that might relate to tuples, if at all.


@jcouv commented on Sat Apr 08 2017

@gafter Should this be moved to csharplang?


@gafter commented on Sun Apr 09 2017

@jcouv yes, I'll move it.

@jnm2
Copy link
Contributor

jnm2 commented Apr 10, 2017

I'd like to see using directive aliases for all types, including generic constructions. Tuples would be one particular case of that.

@mattwar
Copy link
Contributor

mattwar commented Apr 14, 2017

Global usings sounds like something I would use. I like the generic declaration too.. Let's turn usings into full on type aliases!

@jnm2
Copy link
Contributor

jnm2 commented Apr 14, 2017

I want two things for two different reasons:

  1. Using directives, only in effect at compile time and no trace of them in the compiled output
  2. Type aliases, in effect at run time, the use of which would enable types to change namespace and name while staying binary compatible.

@ghost
Copy link

ghost commented Apr 23, 2017

Regarding this:
using Subject = (bool Gender, double Age);

Can this be added in something like C# 7.1? I mean it is necessary ASAP.

We can declare tuple like this:
using Subject = ValueTuple<bool, double>;

And work with (bool Gender, double Age) as Subject
But it this case we are loosing names of fields!

Otherwise we will have to use structs as usual... no benefits from new tuples...

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

I disagree. I'm using tuples over structs to great effect already. Though I am looking forward to using aliases in general, including this.

@ghost
Copy link

ghost commented Apr 24, 2017

I disagree. I'm using tuples over structs to great effect already. Though I am looking forward to using aliases in general, including this.

If you want to abandon your code clarity it is your choice! But stop propagation of that!

This code de-facto is much less readable without tuples aliases:

if (this.Dictionary.TryGetValue(someuser.Code, out (string Name, string Surname, bool Active) value))
{
}

So I will repeat my question.. Why can we do this?

using Setup = Dictionary<string, (string Name, string Surname, bool Active)>;

But not this?

using Value = (string Name, string Surname, bool Active);
using Setup = Dictionary<string, Value>;

if (this.Dictionary.TryGetValue(someuser.Code, out Value value))
{
}

Or at least this?

using Value = (string Name, string Surname, bool Active);
using Setup = Dictionary<string, (string Name, string Surname, bool Active)>;

if (this.Dictionary.TryGetValue(someuser.Code, out Value value))
{
}

@DavidArno
public global Subject<T> = (T Gender, double Age);

It should NOT be global! It is local alias of type and should be local!

@DavidArno
Copy link

DavidArno commented Apr 24, 2017

@HardHub,

It's clear you want these local aliases. What's way less clear to me, is what use they'd be. I've never used a type alias and really cannot see how your dictionary example would improve my code. Tuples though have obvious use cases in my view and are the single best feature of C#7.

I'm looking forward to records, but not this alias suggestion.

@ghost
Copy link

ghost commented Apr 24, 2017

What's way less clear to me, is what use they'd be

As I directly specified above: for code clarity...
Let's say it will be 10 parameters not 3...
Then structs becomes again more and more actual.
I tried new tuples to replace structs in my projects and realized that I cannot get the same clarity without aliases.

cannot see how your dictionary example would improve my code.

It is not about your code it is about my code.. which can look like example provided above...
So does scrolling in GitHub example not make any sense for you?

@DavidArno
Copy link

DavidArno commented Apr 24, 2017

As I directly specified above: for code clarity...

If you have 10 parameters, creating an alias would be sticking a plaster over the infected cut. That's not clarity; that's denial. If you have a 10 parameter tuple then it's time to pull up those refactoring tools and get addressing that.

And for your example, I'm not sure you have understood how to use tuples as you appear to be fighting them, rather than working with them. A simple way to do what you are trying to do, is:

class C
{
    private Dictionary<string, (string Name, string Surname, bool Active)> _dictionary = ...

    void M()
    {
        if (_dictionary.TryGetValue("a", out var value))
        {
           // value.Name, value.Surname and value.Active are all in scope here
        }

    ...
}

@ghost
Copy link

ghost commented Apr 24, 2017

If you have 10 parameters, creating an alias would be sticking a plaster over the invected cut. That's not >clarity; that's denial. If you have a 10 parameter tuple then it's time to pull up those refactoring tools and >get addressing that.

I do not agree. if we need 10 parameters we still need 10 parameters.. And it should be represented in most clear way available in language for the moment. And tuples without aliases are not the ones. For now it is solved with structs, but it is additional definition which I want to avoid with aliases and new tuples.

private Dictionary<string, (string Name, string Surname, bool Active)> _dictionary =

You will have to specify this tuple everywhere... in each method accepting this value.. or returning this value... in other words in all code related to this tuple or this dictionary. Once again it is question of clarity... Anything complex (massive) is not clear by its definition.

if (dict.TryGetValue("a", out var value))

Nice trick... But...

  1. I prefer solution instead of tricks... Using of tuples aliases is very natural since it is existing for other types.
  2. Using "var" everywhere is evil... it is OK only in cases when it is strongly mandatory.
    We will not discuss that here... you can google different articles why var is not accepted... I can only confirm this fact from point of view of my own experience.

@DavidArno
Copy link

Using "var" everywhere is evil...

Ah, I see the problem. What I see as the correct way to use tuples, you see as "tricks". What I see as the best way to write code, you see as "evil". What you see as "clarity", I see as badly written code. Clearly our views are too divergent at a fundamental level for any further meaningful discussion.

@DavidArno
Copy link

DavidArno commented Apr 24, 2017

Reading back through this thread is an interesting view on how my take on some ideas changes over time. I started out liking the idea of being able to express something like:

// Subject.cs
global using Subject<T> = (T Gender, double Age);

Then I decided that there was little point to this if we had records. I now find this view reversed: there actually seems little point to records if we can have global aliases like the above.

@GeirGrusom
Copy link

there actually seems little point to records if we can have global aliases like the above.

Metadata. Conversion operators. Interface implementations.

@ghost
Copy link

ghost commented Apr 24, 2017

What I see as the correct way to use tuples, you see as "tricks". What I see as the best way to write code, you see as "evil". What you see as "clarity", I see as badly written code.

I do not see any point to treat your opinion as correct.
You ignored absolutely all explanations and just try to say if it is not like I am doing that then all others are idiots.

Just one very simple thing to show your manner of discussion:
I said: Using of tuples aliases is very natural since it is existing for other types.
For me it is argument because it is unified approach for all data types possible in C#.
So why not for tuples? Any your arguments why it should not be this way?

P.S.
Do you think thousands of developers said that var is evil because they are idiots? Or maybe they have some point of view which you cannot simply understand? What is most probable?

@DavidArno
Copy link

@GeirGrusom,

Metadata. Conversion operators. Interface implementations.

In my experience, conversion operators are more trouble than they are worth. Why would you want a pure data type to implement an interface? Metadata? Maybe...

@DavidArno
Copy link

DavidArno commented Apr 24, 2017

@HardHub,

You may find it useful to read up on consensus fallacies. Whilst you are at it, it might help to read about false dichotomies too.

@GeirGrusom
Copy link

In my experience, conversion operators are more trouble than they are worth. Why would you want a pure data type to implement an interface? Metadata? Maybe...

Conversion operators can be useful for value types. For example public struct Id<TEntity>(Guid Id) which would allow you a typesafe ID for database items it could be useful to have an explicit cast operator to Guid and vice-versa.

For the interface I guess you could provide an IDeepClone interface, or IWith. It's really difficult to pretend to know every use-case for this, but there definitely are some.

Metadata I think is obvious since ORM objects is an obvious use-case for records.

@DavidArno
Copy link

@GeirGrusom,

Damn, I might have to flip-flop again! 😀

@GeirGrusom
Copy link

Speaking of Id<TEntity> there might be a use-case for exporting aliases as well.

public struct Id<TEntity, TId>(TId Value);
using Gid<TEntity> = Id<TEntity, Guid>;
using CustomerId = Id<Customer, long>;

public class Api
{
  public SynergizeCustomerRelation(CustomerId id);

  public CorrelateProfitAgility(Gid<Quarter> quarter);
}

It could be useful to have those type aliases available to API consumers as well.

@ghost
Copy link

ghost commented Apr 24, 2017

You may find it useful to read up on consensus fallacies. Whilst you are at it, it might help to read about false dichotomies too.

Exactly what I want to say.. You do not have any arguments even in very simple technical questions. And you try to guard yourself with high-flown words!

Non limitus hominus dolboebus!

@DavidArno
Copy link

DavidArno commented Apr 24, 2017

@GeirGrusom,

Nice example that actually shows the benefit of having both records and global type aliases.

This could be achieved using public to denote it's an exported alias:

public struct Id<TEntity, TId>(TId Value);
public using Gid<TEntity> = Id<TEntity, Guid>;
public using CustomerId = Id<Customer, long>;

public class Api
{
  public SynergizeCustomerRelation(CustomerId id);

  public CorrelateProfitAgility(Gid<Quarter> quarter);
}

Could use fairly standard scoping rules:

using Alias = Type; // local to file
private using Alias = Type; // same as above; "private" thus optional as it's the default
internal using Alias = Type; // alias exposed to this project/assembly
public using Alias = Type; // alias exposed to all consumers of this assembly

@HaloFour
Copy link
Contributor

Also remember that aliases can't reference other aliases, only fully qualified types. So you wouldn't be able to alias a named tuple and then use that alias as generic type arguments in other aliases. You'd need to submit another proposal to expand those rules, which will likely have to include ways that the compiler can avoid circular references.

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

@HardHub Using var is a a valid and established practice designed specifically to address this very scenario. For all the years we've used anonymous type projections, we've had no choice but to use var.

@ghost
Copy link

ghost commented Apr 24, 2017

@jnm2, anonymous types is single valid usage for var.. for all other cases it is bad practice...
Anonymous types are created during compilation.. for all other cases you know type and it should be used explicitly. But I do not want to discuss it now.. there are lot of information in the internet why it is bad ti use var...

I am just voting to add aliases for tuples as it is now for all other types...

You can do this:
using MyValue = ValueTuple<int, int, int>;

but not this:
using MyValue = (int CustomName1, int CustomName2, int CustomName3);

I think it is ridiculous...

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

@HardHub I agree that aliases would be useful. There is plenty of developer consensus on both sides of the var issue. If I had to guess from all my personal interactions and the articles I've read, I'd say var has become the majority position. But each is a valid point of view with plenty of people behind it. I make it a rule to always use var for the same reasons people make it a rule to always use auto in C++. Forced initialization encourages single assignment and a more immutable style of programming. To me var is more readable, maintainable and stylish. But even though I can't relate, I know there are downsides to var for some people too and that's okay.

@DavidArno
Copy link

To me var is more readable, maintainable and stylish

I can't remember which of the language team recently said it, but a good rule of thumb here is:

  1. If most developers will read your code in an IDE, use var. It cuts down noise and improves readability, without reducing discoverability.
  2. If most developers will read your code outside an IDE (eg the BCL code in Github), then avoid var as explicit typing carries more information and improves discoverability.

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

See, as to #2, I'd say that to properly understand the returned value's type in context, you'd want to start by look at the method returning it anyway. Once you do that, the type name is right in front of you. 😈

(In my experience there are very few times outside the IDE where if the type isn't obvious I don't end up having to look at the method for other reasons too.)

@kofifus
Copy link

kofifus commented Jan 11, 2019

Would be great to have this in v 7.x .. as it now seems v8 is late and may not include records ..

@yaakov-h
Copy link
Member

A C# release is never late, nor is it early, it arrives precisely when it means to.

@kofifus
Copy link

kofifus commented Jan 11, 2019

(like Chuck Norris ...)

@mlhpdx
Copy link

mlhpdx commented Jan 24, 2019

@HardHub,

It's clear you want these local aliases. What's way less clear to me, is what use they'd be. I've never used a type alias and really cannot see how your dictionary example would improve my code. Tuples though have obvious use cases in my view and are the single best feature of C#7.

I'm looking forward to records, but not this alias suggestion.

I am very much looking forward to it.

Clarity. Repeated type (tuple) declarations require readers to carefully inspect each to ensure they are identical.

Efficiency. Edit once, change everywhere.

Consistency. The current limitation on aliasing tuples (but not generic that use tuples?!) is just one more inconsistency that folks encounter in learning C# today.

@ghost
Copy link

ghost commented Mar 4, 2019

Why is not this proposal a champion?

@jcouv
Copy link
Member

jcouv commented Mar 4, 2019

Why is not this proposal a champion?

No LDM member has felt passionate enough about this proposal yet to take ownership and drive it. Most likely there are other proposal that they feel are more worthwhile at this point.
One way to help is to voice your interest in the proposal and explain why it is important and why you care.

@ghost
Copy link

ghost commented Mar 4, 2019

@jcouv
This proposal solves the issue I posted in #1547 , and is better than my own proposal
Instead writing:

protected List<(Char from, Char to)> Ranges = new List<(Char from, Char to)>();
public CharClass(params (Char from, Char to)[] ranges){
}

it can be:
using Range = (Char from, Char to)

protected List<Range> Ranges = new List<Range>();
public CharClass(params Range[] ranges){
}

@CyrusNajmabadi
Copy link
Member

@MohammadHamdyGhanem I would recommend participating in these discussions over at gitter.im/dotnet/roslyn or gitterim/dotnet/csharplang. People will be able to answer your questions quickly and you can hvae the sort of discussions you seem to want to participate in. Cheers!

@IanKemp
Copy link

IanKemp commented Jul 31, 2019

Still no LDM champion for this? :(

@dustinlacewell
Copy link

Pls.

@Thaina
Copy link

Thaina commented Mar 31, 2020

I want to retracted my against on global alias

Currently now I have working on idea to make a double typed Vector library that utilize tuple instead of specific struct. And so I have many function that look like this

public namespace DoubleNumerics
{
	public static class Vector2Tuple
	{
		public static double Distance(this in (double X,double Y) min,in (double X,double Y) max) => Math.Sqrt(min.DistanceSquare(max));
		public static double DistanceSquare(this in (double X,double Y) min,in (double X,double Y) max)
		{
			var (x,y) = max.Remove(min).Pow(2);
			return x + y;
		}

		public static (double X,double Y) Pow(this in (double X,double Y) value,double p) => (Math.Pow(value.X,p),Math.Pow(value.Y,p));
		public static (double X,double Y) Add(this in (double X,double Y) left,in (double X,double Y) right) => (left.X + right.X,left.Y + right.Y);
		public static (double X,double Y) Remove(this in (double X,double Y) left,in (double X,double Y) right) => (left.X - right.X,left.Y - right.Y);
		public static (double X,double Y) Average(this in (double X,double Y) left,in (double X,double Y) right) => ((left.X + right.X) / 2,(left.Y + right.Y) / 2);
	}
}

This would be great if I could global using on the namespace level. So I could write these function with global using Vector2 = (double X,double Y); across multiple file in the same namespace

@michal-ciechan
Copy link

I think global aliases are a great idea. I've had to reuse an alias before in multiple files and it's annoying to have to keep typing or copy and pasting. Plus, then we'd only have to change the alias value in one place, instead of 20.

But then I would want namespace scoped aliases so that when I have a clash of class names. I can

namespace BusinessLogic
{
    global using User = Core.User   
}

And then everything underneath BusinessLogic will default to Core.User.

But personally I am with @madelson and 100% believe that adding a simple type alias local to a file is the best approach and a step in the right direction.

I believe globals aliasing should be a separate feature, as I think it should potentially affect all type aliases, not just value tuples.

For now worst case scenario you can do ReplaceAll if it changes. not a massive deal. better than currently reading really long method/field declarations.

As a workaround you can always create a custom struct. using record types is a no go for me in a lot of cases, as it looks like its class based.

@IAmTheCShark
Copy link

I just want to point out something I couldn't find in the discussion so far.
It is allready possible to use named tuples in those aliases, right now they do not work in a stand alone fashion, but they do for generic arguments.

This works:
using Bananas = System.Collections.Generic.IEnumerable<(int a, int b)>

this doesnt (just for completenes)

using Banana = (int a, int b)

and this would be really nice

    using Banana = (int a, int b)
    using Bananas = System.Collections.Generic.IEnumerable<Banana>

So it feels a little like an oversight to me.
Also the requirement for fully qualified names is a bit messy.

@tullo-x86
Copy link

This gets even more ironic when you use an even-simpler generic like Nullable<T>:

using Foo = System.Nullable<(string Key, object Value)>;

class Bar
{
	private Foo baz = ("A", new object());
	private Foo qux = null;
}

That said, since the language team seems to care more about records going forward, I'll probably just ignore tuples entirely from here on out. As long as something works 🤷🏼‍♂️.

@CyrusNajmabadi
Copy link
Member

That said, since the language team seems to care more about records going forward

I am already championing the feature to address this :)

@jnm2
Copy link
Contributor

jnm2 commented May 12, 2021

(#4284 tracks the fact that Andy and Cyrus are championing this)

@333fred
Copy link
Member

333fred commented May 12, 2021

I'm going to close this as a duplicate of #4284.

@333fred 333fred closed this as completed May 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests