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

[Proposal]: Support generics and generic type parameters in aliases #1239

Open
gafter opened this issue Jan 8, 2018 · 36 comments
Open

[Proposal]: Support generics and generic type parameters in aliases #1239

gafter opened this issue Jan 8, 2018 · 36 comments

Comments

@gafter
Copy link
Member

gafter commented Jan 8, 2018

@HaloFour commented on Fri Jul 17 2015

This is an alternative proposal to #116.

I propose the following changes to aliases to support generics:

using-alias-directive:
   using identifier = namespace-or-type-name ;
   using identifier type-parameter-list opt = class-type* ;

* There's probably a better item in the spec for this.

The purpose of this proposal is to allow aliases to be defined for unbound or partially bound generic types where the alias is then treated as a generic type that must be closed at the point at which it is used.

For example the following is legal in C# today:

using IntList = System.Collections.Generic.List<int>;

This expansion would permit the following:

// unbound
using MyList<T> = System.Collections.Generic.List<T>;
// partially bound
using StringDictionary<TValue> = System.Collections.Generic.Dictionary<String, TValue>;

The name of the generic type parameter can be anything. The arguments are matched by their position in the aliased type.

using FooList<FOO> = System.Collections.Generic.List<FOO>;
using ReorderedParameterDictionary<TValue, TKey> = System.Collections.Generic.Dictionary<TKey, TValue>;

When consuming the alias the rules would apply the same as when consuming a generic type:

MyList<int> list = new MyList<int>();
Debug.Assert(list is System.Collections.Generic.List<int>);

StringDictionary<bool> dict = new StringDictionary<bool>();
Debug.Assert(dict is System.Collections.Generic.Dictionary<string, bool>);

ReorderedParameterDictionary<int, string> dict2 = new ReorderedParameterDictionary<int, string>();
Debug.Assert(dict2 is System.Collections.Generic.Dictionary<string, int>);

Arguments:

  1. This syntax cannot be assembled from existing syntax within the specification thus requiring additional work.

    This is true. Even though I provide a syntax above it is likely incorrect and the feature probably requires some new syntax. I believe that this is worth it for the functionality that the feature provides and I also feel that this syntax is more intuitive to the developer than the alternatives.

  2. Requiring the developer to name the generic type arguments is confusing.

    Naming generic type arguments is a very common task for anyone who has to write a generic type or generic method today. This should be somewhat second nature. This proposed syntax is very similar to the same syntax that would be required to define a new generic class with a generic subclass.

  3. Requiring the developer to name the generic type argument requires more keystrokes.

    This would require more keystrokes than simply using the existing unbound type syntax. However, the developer is not bound to using the same generic type parameter names as the type that is being aliased making the required number of additional keystrokes only one per generic type parameter. Given the common convention for generic type parameters is a single character, e.g. T, I don't think that this places an undue burden on the developer defining the alias nor any developer who must read and maintain that alias. It also represent a very small price to pay for the keystroke benefits that the feature would provide.

Complications:

  1. Generic aliasing could lead to recursion, e.g. using MyList<T> = System.Collections.Generic.List<MyList<T>>;.

The rules for aliases today already solves this problem by not permitting an alias to refer to itself or another alias. If that weren't the case this scenario would already be problematic: using MyList = System.Collections.Generic.List<MyList>;
2. The alias can represent a partially open generic type which is problematic if the developer attempts to obtain a Type instance from the alias via typeof().

This is true. Given the StringDictionary<T> alias defined above it would be a problem to attempt to obtain a Type reference using typeof(StringDictionary<>). In my opinion this could be solved in one of two possible ways:

  1. Make it illegal to use the unbound type definition of the alias. The expression typeof(StringDictionary<>) would be a compile-time error, however the expression typeof(StringDictionary<bool>) would be valid and equivalent to typeof(Dictionary<String, bool>). This might be confusing if aliases to an unbound type were supported, e.g. typeof(MyList<>).

  2. Support partially open Type references. This requires a little more code generation but is perfectly legal:

    Type type = typeof(StringDictionary<bool>);
    // equivalent to:
    Type $temp1 = typeof(Dictionary<,>);
    Type[] $temp2 = $temp1.GetGenericArguments();
    Type type = $temp1.MakeGenericType(typeof(string), $temp2[1]);

    I am including this potential solution for the sake of argument. While being able to support syntax typeof(StringDictionary<>) would reduce the confusion for the developer I think that same developer would be more confused if the result is a partially open generic type.


@HaloFour commented on Fri Jul 17 2015

Alternatively, as an addendum to the syntax proposed by #116 this could be changed to use partially bound type names. If that proposal were implemented this is likely the changes that would need to be made to it in order to add support for partially bound aliases short of having two separate generic alias syntaxes:

using StringDictionary<> = System.Collections.Generic.Dictionary<string,>;

@sharwell commented on Fri Jul 17 2015

I don't see this proposal as an alternative to #116, but rather as a separate feature request intended to solve a different problem -- specifically the ability to use partially-bound generic using directives.


@HaloFour commented on Fri Jul 17 2015

@sharwell

Perhaps. Given the overlap between the two I feel that it is probably more appropriate to approach them as alternatives. If #116 were to be implemented (or was already implemented) I don't feel that a completely separate syntax such as this would be appropriate and I would instead be pushing for further evolution of your proposal in a similar vein to the first comment I made to this proposal.


@whoisj commented on Wed Jul 22 2015

@HaloFour 👍 I love this. I would use it constantly.


@aluanhaddad commented on Mon Jul 27 2015

👍 This is an excellent idea.


@zippec commented on Tue Aug 04 2015

I believe there is a problem with proposed syntax:

using StringDictionary<TValue> = System.Collections.Generic.Dictionary<String, TValue>;

because there is no way for the compiler (other than convention) to tell if it should look up type TValue or if it is unbound. Thus the syntax in #116 is an obvious choice:

using StringDictionary<> = System.Collections.Generic.Dictionary<string,>;

EDIT: I take that back. Of course the compiler can match generic parameter type name from alias to generic parameter type name of aliased type. This will even allow reordering of type parameters.

using MyDictionary<TValue, TKey> = System.Collections.Generic.Dictionary<TKey, TValue>;

@HaloFour commented on Tue Aug 04 2015

@jveselka

I had used TValue as the name of the generic type parameter just to keep with convention. The name should be anything and the type arguments matched by position. I'll update the proposal to make that more clear. Thanks.


@sharwell commented on Tue Aug 04 2015

This proposed syntax is very similar to the same syntax that would be required to define a new generic class with a generic subclass.

But the proposed syntax is a reference to an existing type, not the declaration of a new type. In C#, the only way to reference an open generic type in code is by omitting the type arguments (e.g. typeof(Dictionary<,>)). This proposal increases developer confusion surrounding an already poorly understood feature by creating a second way to do the same thing.¹

By defining the support for aliasing open generic types using the same syntax as typeof, we not only address this functionality in the simplest possible form, but we also improve developer familiarity and understanding of the prior feature.

¹ I'm ignoring XML documentation comments here, which have their own syntax.


@HaloFour commented on Tue Aug 04 2015

@sharwell

Why does aliasing need to be artificially limited by being intrinsically tied to typeof? You don't use typeof when defining the alias in any situation, let alone with generic types. The fact that the syntax smells like that used with typeof is not relevant, they don't refer to the same grammars in the specification, accidentally or intentionally. You can't alias void, but you can typeof(void). I see no reason whatsoever to conflate the two. Doing so would only increase developer confusion while simultaneously neutering the functionality of aliasing generic types.

This proposal is simply more intuitive and more functional. It solves the common use case of aliasing partially open generic types such as Dictionary<string,> that cannot be defined through typeof or your proposal. It doesn't have a byproduct of educating the developers about typeof, and it shouldn't.

We're simply going to have to agree to disagree. That is why I submitted a separate proposal.


@paulomorgado commented on Tue Aug 04 2015

Given:

using StringDictionary<TValue> = System.Collections.Generic.Dictionary<String, TValue>;

What is typeof(StringDictionary) supposed to be?


@HaloFour commented on Tue Aug 04 2015

@paulomorgado

typeof(StringDictionary) would be a compiler error since StringDictionary does not exist.

However, typeof(StringDictionary<>) is a different story and one that I explicitly mention in the complications section of the proposal. I put out two potential solutions for the sake of argument, but I am definitely in the first camp where the expression typeof(StringDictionary<>) results in a compiler error.


@TyreeJackson commented on Thu Aug 06 2015

👍 I also agree that this is an excellent idea. I've often wanted to do precisely what this proposal asks for.


@alrz commented on Thu Dec 03 2015

There is no way to get this out of file scope?


@aluanhaddad commented on Fri Dec 04 2015

@alrz The scope would, l imagine, be consistent with other using directives.


@alrz commented on Fri Dec 04 2015

I mean something like this,

using class EmailAddress = String;
// or 
class EmailAddress = String;

so it'll be in scope in all files.


@HaloFour commented on Fri Dec 04 2015

@alrz Sounds like that should be a separate proposal entirely. I imagine it would be a compiler directive, as it is in VB.NET:

/imports:"Foo = System.Int32"


@alrz commented on Fri Dec 04 2015

@HaloFour I wonder how F# is doing this for type abbreviations like seq<'T>; does it expose these aliases, or seq<'T> is a special case?


@HaloFour commented on Fri Dec 04 2015

@alrz The is no concept of aliases in the CLR. Every compiler that supports the notion does so in its own fashion and entirely internally. F# seems to treat seq<'T> as an alias for System.Collections.Generic.IEnumerable<T>, which isn't really different from C# treating string as an alias for System.String or VB.NET treating Date as an alias for System.DateTime. A custom type alias in F# isn't encoded in any fashion in the resulting assembly anymore than it is in C# or VB.NET.


@wizzardmr42 commented on Sun Jan 03 2016

@aluanhaddad It would also be useful to take it across assembly boundaries and use it without creating a named alias, neither of which are do-able with aliasing.


@aluanhaddad commented on Sun Jan 03 2016

@wizzardmr42 That sounds like an orthogonal feature.


@wizzardmr42 commented on Sun Jan 03 2016

@aluanhaddad Sorry - just pointing out that wouldn't be a complete solution for everything discussed in #3281, which referenced this


@HaloFour commented on Mon Jan 04 2016

@wizzardmr42

I was commenting specifically on the mention of the current limitations with type aliases. It was someone else who had suggested that type aliases could be an alternative workaround to the original proposer's problem.

This proposal is only a modest extension of existing aliases in C# and does not touch on the subject of extending them beyond the current code file. @alrz has proposed #7451 to extend aliases throughout the current project using code notation. I personally prefer VB.NET's way of handling that, by using command line arguments to the compiler (exposed through the project configuration) to establish project-wide aliases.

For something beyond the current project there could either be tooling to help configure multiple projects within a solution, or there would need to be a convention of embedding some kind of metadata in the assemblies to declare aliases. That concept seems very messy as it sounds like it would establish a second type system within the compiler that would be erased by compilation and ignored by the CLR.


@wizzardmr42 commented on Mon Jan 04 2016

Sorry - I actually meant to post that on #3281, but it is worth pointing out here too that being able to reference existing types using a "typeof" keyword would solve some of the same problems that this would so may be worth looking at #3281 before implementing this (and #7715 which is a duplicate, but I gave more specific cases where it would be useful there).


@paulomorgado commented on Mon Jan 04 2016

@HaloFour,

(...) I personally prefer VB.NET's way of handling that, by using command line arguments to the compiler (exposed through the project configuration) to establish project-wide aliases.

For something beyond the current project there could either be tooling to help configure multiple projects within a solution, or there would need to be a convention of embedding some kind of metadata in the assemblies to declare aliases. That concept seems very messy as it sounds like it would establish a second type system within the compiler that would be erased by compilation and ignored by the CLR. (...)

One immediate benefit of project-wide vs. solution-wide aliases is the possibilty of shared source files binding to different types according to the project they are being compiled against without having them poluted with conditional compiler directives. Atlthough that would erase the obiousness of the per-project semantics.


@alrz commented on Tue Jan 05 2016

@paulomorgano I didn't think of that! It would be a pleasant workaround for #7451 in case of need for solution-wide aliases.


@TonyValenti commented on Thu Feb 04 2016

Hi All,
I was just reading this and this is something that I would love - especially if it had the ability for me to expose the alias into derived classes.

The example that I deal with a lot is related to refactoring generic classes that inherit from other generic classes. Take this example below:

public class BaseClass<T, U> {
    public virtual void DoSomething(T Parameter){

    }
}

public class OriginalParameterType { }
public class RefactoringParameterType { }

public class SpecificImplementation<U> : BaseClass<SpecificParameter, U> {
    public override void DoSomething(SpecificParameter Parameter) {
    }
}

If I want to change SpecificImplementation to inherit from BaseClass<RefactoringParameterType, U>, I then have to dig through the source code (and any other classes that inherit from SpecificImplementation<U>) and change the method's signature to be:

public override void DoSomething(SpecificParameter Parameter) { }

In order to get around that, I'll usually create a type that simply inherits from one of my generic parameters. For example:

public class BaseClass<T, U> {
    public class MyParameterType : T { }

    public virtual void DoSomething(MyParameterType Parameter){

    }
}

public class OriginalParameterType { }
public class RefactoringParameterType { }

//If I replace OriginalParameterType with RefactoringParameterType I don't have to manually refactor all my method definitions!  :-)
public class SpecificImplementation<U> : BaseClass<OriginalParameterType , U> {
    public override void DoSomething(MyParameterType Parameter) {
    }
}

When I do this, all I'm really doing is creating an alias for T that gets propagated down into child classes. In most cases, I really prefer that because I don't have to deal with giant generic definitions and it makes refactoring possible, however, the one downfall of doing things the way that I am is that MyParameterType is not assignable from a <T>.

I love a construct that would allow me to create a true alias for a type and pass that down into derived or other classes.

For example, here is a thought:

public class BaseClass<T, U> {
    //I define the alias here.
    public alias ParameterType: T;

    //Notice that I'm using it here instead of <T>
    public virtual void DoSomething(ParameterType Parameter){

    }
}

public class OriginalParameterType { }

public class SpecificImplementation<U> : BaseClass<OriginalParameterType , U> {
    //And in derived methods, the signature gets exposed the alias: 
    public override void DoSomething(ParameterType Parameter) {
    }

}

....

//And I should even be able to reference the alias in other methods:
var derived = new SpecificImplementation<string>();

//Pull the type off of an instance.
var instance1 = new derived.MyParameterType();

//Pull the type off of the class.
var instance2 = new SpecificImplementation<string>.MyParameterType();
....




@aluanhaddad commented on Thu Feb 04 2016

@TonyValenti While that is certainly a scenario I would like to see supported, it seems like a perfect use case for a custom-refactoring, and not a language feature. This proposal is nicely focused and scoped (no pun intended), adding significant value while impacting little else in the language. That said, I think the scenario you mention is very compelling and I would find it very useful, but it could be a refactoring. That said, if it naturally falls out of this proposal, so much the better.


@dsaf commented on Thu Sep 15 2016

The resemblance is eerie: https://github.com/apple/swift-evolution/blob/master/proposals/0048-generic-typealias.md

typealias DictionaryOfStrings<T : Hashable> = Dictionary<T, String>
using StringDictionary<TValue> = System.Collections.Generic.Dictionary<String, TValue>;
@yaakov-h
Copy link
Member

yaakov-h commented Jan 8, 2018

Duplicate of #90.

@MaStr11
Copy link

MaStr11 commented Jan 8, 2018

This would also help to resolve ambiguity problems as in dotnet/roslyn#24022 (See also this comment in the PR).

@bondsbw
Copy link

bondsbw commented Jul 18, 2018

When combined with #218, the generic type parameters of the enclosing scope should be available to the alias.

This was discussed a bit on Gitter. Starting from an example on @DavidArno's SuccincT project:

internal sealed class ValueOrErrorMatcher<TResult> : IUnionFuncPatternMatcherAfterElse<TResult>, IValueOrErrorFuncMatcher<TResult>, IValueOrErrorActionMatcher, IUnionActionPatternMatcherAfterElse
{
...

    IUnionFuncPatternCaseHandler<IValueOrErrorFuncMatcher<TResult>, string, TResult>
        IValueOrErrorFuncMatcher<TResult>.Value() =>
            new UnionPatternCaseHandler<IValueOrErrorFuncMatcher<TResult>, string, TResult>(RecordValueAction, this);

    IUnionFuncPatternCaseHandler<IValueOrErrorFuncMatcher<TResult>, string, TResult>
        IValueOrErrorFuncMatcher<TResult>.Error() => 
            new UnionPatternCaseHandler<IValueOrErrorFuncMatcher<TResult>, string, TResult>(RecordErrorAction, this);

    IUnionFuncPatternMatcherAfterElse<TResult> IValueOrErrorFuncMatcher<TResult>.Else(TResult value)
    {
        _elseAction = _ => value;
        return this;
    }

    IUnionFuncPatternMatcherAfterElse<TResult> IValueOrErrorFuncMatcher<TResult>.Else(
        Func<ValueOrError, TResult> elseAction)
    {
        _elseAction = elseAction;
        return this;
    }

...
}

TResult from the class definition is available and can be used in aliases within that scope:

internal sealed class ValueOrErrorMatcher<TResult> : IUnionFuncPatternMatcherAfterElse<TResult>, IValueOrErrorFuncMatcher<TResult>, IValueOrErrorActionMatcher, IUnionActionPatternMatcherAfterElse
{
    using IVOEMatcher = IValueOrErrorFuncMatcher<TResult>;
    using IUFuncPCHandler = IUnionFuncPatternCaseHandler<IVOEMatcher, string, TResult>;
    using IUFPMatcherAE = IUnionFuncPatternMatcherAfterElse<TResult>;
    using UPCHandler = UnionPatternCaseHandler<IVOEMatcher, string, TResult>;

...

    IUFuncPCHandler IVOEMatcher.Value() => new UPCHandler(RecordValueAction, this);

    IUFuncPCHandler IVOEMatcher.Error() => new UPCHandler(RecordErrorAction, this);

    IUFPMatcherAE IVOEMatcher.Else(TResult value)
    {
        _elseAction = _ => value;
        return this;
    }

    IUFPMatcherAE IVOEMatcher.Else(Func<ValueOrError, TResult> elseAction)
    {
        _elseAction = elseAction;
        return this;
    }

...
}

@theraot
Copy link

theraot commented Aug 27, 2019

I found myself wanting to do something like this:

#if NET30

using Func<TInput, TOuput> = System.Converter<TInput, TOuput>;

#endif

@KillyMXI
Copy link

KillyMXI commented Apr 4, 2020

Any chance we will get this in foreseeable future?

I ran across this when trying to write bare-bones parser combinator with functions like this:

static Func<string[], int, (int next, T result)?> ParseAnyOf<T>(
        params Func<string[], int, (int next, T result)?>[] parsers
        )

The best I can do now is to introduce a struct for (int next, T result). That will half-clean type definitions (but will also require some boilerplate at the return location, for better or worse).

Ideally, for a functional style code we should be able to give names to reoccurring function "shapes".

using Result<T> = (int next, T value);
using ParseFunc<T> = Func<string[], int, Result<T>?>;

static ParseFunc<T> ParseAnyOf<T>(params ParseFunc<T>[] parsers) => ...

@Joe4evr
Copy link
Contributor

Joe4evr commented Apr 4, 2020

@KillyMXI

Any chance we will get this in foreseeable future?

Not without a proposal Champion.

@KillyMXI
Copy link

KillyMXI commented Apr 4, 2020

Did this answer add something? I can only paraphrase my question:

Any chance this will get a proposal Champion?

For the amount of requests it's surprising it stays like this.

@HaloFour
Copy link
Contributor

HaloFour commented Apr 4, 2020

@KillyMXI

Any chance this will get a proposal Champion?

Only members of the language team can say for sure, and only really through either championing it or by outright closing it. A lot of proposals end up in this limbo and it's a bit unfortunate but that kind of happens when you have a relatively small team and limited resources but allow the public to pile onto a near infinite backlog. Not to mention the desire to intentionally be very conservative and deliberate about the change accepted into the language. This won't answer your question either, but only someone with a "Member" badge next to their name can give you something closer to an authoritative answer. And even then I wouldn't expect much of a direct answer unless it happens to be, "no."

@gafter gafter self-assigned this Apr 4, 2020
@gafter gafter added this to TRIAGE NEEDED in Language Version Planning Apr 4, 2020
@333fred 333fred added this to the 10.0 candidate milestone Jul 13, 2020
@333fred 333fred moved this from TRIAGE NEEDED to 10.0 Candidate in Language Version Planning Jul 13, 2020
@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Jul 16, 2020

This is an alternative proposal to #116.

That presumably refers to dotnet/roslyn#116.

Are aliases going to be overloadable by the number of type parameters? The compiler already allows an alias to have the same name as a type. (If the type is not generic, then the compiler allows the definition, but references may cause error CS0576.)

using Chore = System.Threading.Tasks.Task;
using Chore<TResult> = System.Threading.Tasks.Task<TResult>;
partial class Chore<TResult, TProgress> {}

Is there any surprising interaction with type inference and overload resolution? It seems the compiler could first expand all type aliases and then use its current algorithms.

@333fred
Copy link
Member

333fred commented Jul 16, 2020

Excellent questions, and ones we need to design and answer 🙂

@KalleOlaviNiemitalo
Copy link

using-alias-directive:
using identifier = namespace-or-type-name ;
using identifier type-parameter-list opt = class-type* ;

type_parameter_list can include attributes but I think those should not be allowed here.

As variant_type_parameter_list is not used here, I suppose an alias like using LongCount<T> = System.Func<T, System.Int64>; will have its type parameter treated as contravariant even though it is not declared as LongCount<in T>. Constraints on type parameters will likewise depend on what the alias does with the type parameters. This way, type inference will not be affected by whether a generic type is named directly or via an alias.

If an alias does not use some of its type parameters at all (using Integer<T> = System.Int32;), then I guess that type parameter cannot be inferred through the alias. The compiler could perhaps warn about such an alias declaration.

XML documentation comments for type parameters of aliases can wait until XML documentation comments on non-generic aliases are supported.

@jcouv
Copy link
Member

jcouv commented Sep 28, 2020

When we look at this we should also look at allowing built-in types, e.g. using UserId = int;

@JimmyCushnie
Copy link

I disagree. I think the built-in types should never be disguised, I cannot think of a single example where that would make the code more clear instead of less. In the example you gave, the programmer should make a new class or struct called UserID.

@333fred 333fred modified the milestones: 10.0 candidate, X.0 candidate Sep 28, 2020
@333fred 333fred moved this from 10.0 Candidate to X.0 Candidate in Language Version Planning Sep 28, 2020
@alrz
Copy link
Contributor

alrz commented Sep 29, 2020

When we look at this we should also look at allowing built-in types

Assuming that includes tuples, arrays and pointers.

@gulshan
Copy link

gulshan commented Sep 29, 2020

Kotlin has an experimental "inline class" feature for compile-time-only wrapping of types. So inline class UserId(value: Int) acts as a separate type at dev/compile time, but is just Int at runtime. Details- https://kotlinlang.org/docs/reference/inline-classes.html

@GinoMan
Copy link

GinoMan commented Jan 5, 2021

I have a REALLY good use case. I have a class that contains several different Func<> and Action<> members. I also have methods that take these as parameters, and default methods that can be assigned to those roles (though they don't need to be) So I repeat... Func<Rect, T, Vector2Int, int, T> and Func<int, int, T[,], int> and Action<Rect, T T[,], Vector2Int, int> A LOT. So what happens when midway through writing this class, I need to add a parameter to all these methods, I have to change it EVERYWHERE those types are used. How would I get around this with other classes?

Well, there's...

public class MyType : BaseType<...> { }

But you can't do that with Func and Action because they're "special". Ok. Maybe I can alias the type?

using MyType = BaseType<Rect, T.... oh wait... I need to define what T is since it's not in the context of my class anymore... Ok

using MyType<T> = BaseType<Rect, T.... oh, nope, I can't do that. hmmmm. Maybe I can put it inside the class....

class MyClass { using MyType....

oh crap, another error? cmon! "using declarations must be made at the top of the file before any other declarations" Ugh. Does C# have macros at all like in C++? Nope. Well then what am I supposed to do, I can't keep changing my code in 15-20 different places just to add a parameter!

But no, that's exactly what I have to do. sigh. Why? Why am I not able to do something like...

alias MyClass<T> : Func<...>; ?

or

typedef MyClass<T> Func<...>; ?

C has had typedef since the 70s when it was first invented. So why isn't there a type simplification system that preserves generics in C#?

@theunrepentantgeek
Copy link

There's nothing particularly special about Func and Action - all delegate types are sealed.

But you can define your own:

// Func<Rect, T, Vector2Int, int, T> 
public delegate T Eagle<T>(Rect r, Vector2Int v, int i);

// Func<int, int, T[,], int>
public delegate int Falcon<T>(int i, int j, T[,] matrix);

// Action<Rect, T, T[,], Vector2Int, int>
public delegate void Flamingo<Rect, T T[,], Vector2Int, int>(Rect rect, T t, T[,] matrix, VectorInt v, int k);

I used meaningless bird names because I can't discern what your delegates do ... but I think these are entirely equivalent to what you're wanting to define. No aliases needed.

@markusschaber
Copy link

But you can define your own:

Which has the disadvantage that they're not compatible to the "original" delegate type anymore.

@GinoMan
Copy link

GinoMan commented Jan 14, 2021

Right.

What I'm doing is refactoring several methods with lots of repeated code. So I created a class that does what that code does for me, allowing the user of the class to add custom functionality if they wish, or just change some settings and it will do what they want for each use case. And that's fine but I ran into that problem of "I can't put the type in one spot and change it dynamically while I write this class".

I'm trying to make my code more maintainable and manageable which is the whole reason I wanted to alias the types.

@ekolis
Copy link

ekolis commented Feb 1, 2021

This would be really handy for things like coordinates:

public class Coordinates<TRow, TColumn> {...}

// now I could say this but this is inheritance not an alias so it requires consuming code to actually use the subclass
public class Coordinates2<int> : Coordinates<int, int> {...}
public class IntCoordinates2 : Coordinates2<int> {...}

// this would be so much nicer
// the only problem is, would I have to rewrite these using statements in every code file?
using Coordinates2<T> = Coordinates<T, T>;
using IntCoordinates2 = Coordinates2<int>;

@theraot
Copy link

theraot commented Feb 1, 2021

@ekolis see also #3428 Edit: And also #259 / dotnet/roslyn#7451 I guess.

@alrz
Copy link
Contributor

alrz commented Feb 2, 2021

I see this is not present in the current working set (#4144)?

I think it's a necessity to add generics to the discussion when we talk about "global aliases." this would make it tenfold more useful.

@333fred 333fred removed this from X.0 Candidate in Language Version Planning Feb 6, 2021
@tonygiang
Copy link

tonygiang commented May 6, 2021

+1

First thing I'd do if this was a feature is:

using SortedAction<TKey, T1, T2> = SortedList<TKey, Action<T1, T2>>;

public static class Extensions
{
  public static void Invoke<TKey, T1, T2>(this SortedList<TKey, Action<T1, T2>> source, T1 arg1, T2 arg2)
  { foreach (var action in source.Values) action.Invoke(arg1, arg2); }
}

[...]

SortedAction<byte, string, int> OnEvent = new SortedAction<byte, string, int>();
OnEvent.Add(3, CallbackThird);
OnEvent.Add(1, CallbackFirst);
OnEvent.Add(2, CallbackSecond);
OnEvent.Invoke("foo", 29); // "foo" and "29" are passed in a guaranteed order: CallbackFirst -> CallbackSecond -> CallbackThird

@perrauo
Copy link

perrauo commented Oct 31, 2021

I upvote this.

@nathan130200
Copy link

Any news about this feature? I'm excepting this on C#

@fschwiet
Copy link

This is more desirable now that we have a "global using" language feature.

@IanKemp
Copy link

IanKemp commented Jul 19, 2022

PLEASE.

A legacy monolith I'm working in has a type called Task that is defined in a namespace with many other types, hence this namespace is imported LITERALLY EVERYWHERE. This means that writing async-await code in said monolith is an exercise in frustration; because I cannot write

using Task = System.Threading.Task;
using Task<> = System.Threading.Task<>;

I am forced to fully-qualify the Task that I want EVERY TIME. And there is no appetite for refactoring to rename our internal Task as it's so widely used.

In terms of productivity enhancement this is such a small change that would make such a significant difference to so many C# users. Please, implement it - I beg of you.

@Eishaaya
Copy link

When dealing with subclasses, or smaller types used elsewhere (mainly nodes), I prefer aliasing these types to friendly and short names for sake of convenience. Because of limitations in aliasing, it is impossible to alias a generic without supplying concrete type(s).

Was about to request this, seems I've been beaten to the punch by a mile!
Been wanting this for a while too

@dsaf
Copy link

dsaf commented Aug 13, 2022

Now that global usings are available #3428 defining reusable aliases becomes more attractive for scenarios like internal DSLs. Generics were already added for custom attributes #124. Is it that big of a stretch to extend generics to aliases?

@maettu-this
Copy link

Knowing this discussion is quite old = mature, how about using class|struct|enum rather than the using directive? E.g....
public class IntList = System.Collections.Generic.List<int>;
...rather than...
using IntList = System.Collections.Generic.List<int>;
?

Opposed to the C# 10 global using directive, which is...

allowed only on the Compilation Unit level (cannot be used inside a namespace_declaration)

...using class|struct|enum would allow specifying the scope of the alias: Same as "normal" types, the alias would clearly belong to a namespace. And, making use of existing modifiers public|internal would allow fine tuning of the scope, same as for "normal" types.

@soroshsabz
Copy link

ITNOA

Hi,

Are you think, this feature comes to C# 12?

thanks

@CyrusNajmabadi
Copy link
Member

@soroshsabz No. You can tell this by looking here:

image

@adamal
Copy link

adamal commented Apr 2, 2024

Named delegate types, as suggested once above, may help in some situations, but hardly in the general case.

In case this is left hanging due to technical difficulties, another crutch that could help in a lot more situations would be allowing using sealed classes as type constraints.
The only argument I found for disallowing them is that "they do not make sense, since you cannot inherit from them by definition". I found this out trying to simplify some methods like this:

public static Expression<Func<T, bool>> And<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)

Here the exact same type is repeated three times. I can hardly find the method and parameter names.

Contrast that with this (which is illegal since Expression is sealed)

public static T And<T, T1>(
   this T expr1,
   T expr2) where T : Expression<Func<T1, bool>>   

@MHDante
Copy link

MHDante commented Apr 16, 2024

Just want to add some breadcrumbs here:

Open Proposal PR allowing generics in aliases (not in proposals yet):
#4452

Using alias as a "macro" referenced by the LDM:
https://github.com/dotnet/csharplang/blob/c195f1e44c1285eea67d16aa2d88a1dd577265c1/meetings/2023/LDM-2023-02-01.md

Recent proposal for allowing more types in aliases (shipped in C# 12):
https://github.com/dotnet/csharplang/blob/c195f1e44c1285eea67d16aa2d88a1dd577265c1/proposals/csharp-12.0/using-alias-types.md

Discussion about the proposal:
https://github.com/dotnet/csharplang/blob/c195f1e44c1285eea67d16aa2d88a1dd577265c1/meetings/2023/LDM-2023-01-11.md

Relevant part of the standard (v8 draft):
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#145-using-directives

My understanding from @CyrusNajmabadi 's Post here is that the current proposal avoids generic forwarding on purpose and assuages a large amount of implementation pain points.

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