csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

[Proposal] Add ability to declare global usings for namespaces, types and aliases by using a command line switch

Open AlekseyTs opened this issue 4 years ago • 159 comments

Motivation - provide shared context for the program to reduce repetition of common using directives across all source files. For example, using System;, etc. VB compiler supports that for many years.

Specification: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/GlobalUsingDirective.md

AlekseyTs avatar May 06 '20 01:05 AlekseyTs

I don't see the value-add of this - it lessens the ability of readers to reason about the C# source code their reading by adding mystery.

theunrepentantgeek avatar May 06 '20 02:05 theunrepentantgeek

Far from being mysterious, these chunks at the tops of each file are often large and obvious, and they are rarely interesting to look at. They are a frequent source of noise in diffs where they are usually just as uninteresting as when reading a source file.

There is no option for you if any of this bothers you today. The IDE could have an autocollapse feature for using directives, but that wouldn't do anything to help other experiences. The number of times this has come to mind has overcome the skepticism I had on this a few years ago. I'm happy to see that an option is being provided.

jnm2 avatar May 06 '20 04:05 jnm2

@louthy might find this interesting for his language-ext package. Currently you have to add

using LanguageExt;
using static LanguageExt.Prelude;

To every file. If you could specify in the csproj that every file should have those preimported it would make his language extensions feel much more idiomatic.

YairHalberstadt avatar May 06 '20 04:05 YairHalberstadt

I often came to the point wanting a project wide type alias: using xxx = sometype; This desire was mainly driven by library development purposes, not by usage aspects.

A project wide using static could be smart also. (but I had never a real need for this)

gerhard17 avatar May 06 '20 06:05 gerhard17

I often came to the point wanting a project wide type alias:

I feel less comfortable about this. I feel like if C# ever introduces project wide type aliases, it should be a first class language feature, rather than a compiler switch.

YairHalberstadt avatar May 06 '20 06:05 YairHalberstadt

My opinion: I really like _Imports.razor from ASP.NET Core Razor framework; I'd like to see something similar for C# as well. :)

amis92 avatar May 06 '20 07:05 amis92

Instead of cli option, could be considered as global aliases. There is a handful of proposals in that space already. For instance:

// Imports.cs
global using System;
global using static Helpers;
global using Alias = TypeOrNamespace;

On top of that, https://github.com/dotnet/csharplang/issues/1239 is championed as well which could be used together.

global using ServiceResult<T> = Result<T, ServiceError>;

An important question would be metadata encoding outlined by @333fred in https://github.com/dotnet/csharplang/issues/259#issuecomment-568916708

alrz avatar May 06 '20 10:05 alrz

I also support the concept of global usings in the project file. Although there'd be no way top opt out of these auto-usings per file, so it's possible there'd be clashes. There are ways around this though, so maybe it's not too bad.

Richiban avatar May 06 '20 10:05 Richiban

As noted above, VB.NET already has this (and has since inception), and it has never been considered a source of confusion. Most users would interact with it via their project settings. I personally don't find it to be any different or more confusing than the fact that you can add references from the command line which has just as profound impact on the code you are reading.

@alrz

On top of that, #1239 is championed as well which could be used together.

If that proposal is also implemented it might complicate this proposal as it involves characters that can't be used on the command line. Replacement characters would be required. It wouldn't be a big deal, but something to keep in mind.

🍝

-using:StringDict(T)=System.Collections.Generics.Dictionary(System.String,T)

HaloFour avatar May 06 '20 13:05 HaloFour

I expect generic type aliases to be able to define generic constraints. Also it could be a possiblity to enable "public" type aliases in the future. This proposal doesn't make the transition any easier.

I don't think there's any sane way to encode that in an string except for literally writing a possibly slightly diffrent C# dialect inside the command line, plus you'd lose any IDE goodies like autocomplete or goto declaration. That will need an expensive tooling support for a trasparent experience.

We're practically exposing language semantics outside of the language itself to an external source - this doesn't seem like a good idea IMO.

alrz avatar May 06 '20 15:05 alrz

@alrz

I expect generic type aliases to be able to define generic constrains. Also it could be a possiblity to enable "public" type aliases in the future. This proposal doesn't make the transition any easier.

That's not a part of that championed proposal, although it's fair to bring it up as a possibility. I'd think that this feature would be orthogonal to public aliases. It's also much simpler to implement, and it doesn't necessarily have to support the full breadth that using declarations might support.

That will need an expensive tooling support for a trasparent experience.

VB.NET already (and has always) exposed this option through the tooling experience in Project Settings. IMO that's not a problem.

HaloFour avatar May 06 '20 15:05 HaloFour

I'd think that this feature would be orthogonal to public aliases. It's also much simpler to implement, and it doesn't necessarily have to support the full breadth that using declarations might support.

I believe it's actually very similar to an "internal using directives" feature which itself is a subset of global imports. All the mentioned features could fit nicely in an "enhanced using" umbrella, but this proposal as a command line option does not contribute to that possible set of improvements and likely becomes unpreferable when we have those features in place.

VB.NET already (and has always) exposed this option through the tooling experience

For me, it's understandable if VB does that. I think VB is a lot more "forgiving" in different aspects than C# (more implicit conversions, non-constants in case clauses, to name a few). So it's accepted that we even have a few imports by default. Maybe it worked out well for the target audience, but C# has always been a little more strict.

My argument is that any code that has meaning e.g. affects binding, should be a part of the source. The second you're out of source, the experience tends to degrade, in some sense.

alrz avatar May 06 '20 16:05 alrz

Whether this is implemented via new C# syntax or CLI parameters, the SDK will be able to build on top of this and do <GlobalUsing Include="System" /> csproj items.

jnm2 avatar May 06 '20 16:05 jnm2

Hopefully the LDM discussion on this subject will include global aliases and maybe exported aliases.

IMO the experience isn't any different if the namespaces/aliases are declared in the CLI vs. in a source file that could potentially be very far removed from the source file that you're currently looking at. I stopped using VB.NET around the time that C# 3.0 was released but I do know that I made use of project-wide imports.

HaloFour avatar May 06 '20 16:05 HaloFour

I don't agree with this statement:

As noted above, VB.NET already has this (and has since inception), and it has never been considered a source of confusion.

Even on roslyn itself it has been a point of pain and confusion numerous times :)

CyrusNajmabadi avatar May 06 '20 17:05 CyrusNajmabadi

@CyrusNajmabadi

Even on roslyn itself it has been a point of pain and confusion numerous times :)

Sounds like a good point to bring up in LDM. But is the nature of the feature confusing, or is it confusing because it's an aspect of VB.NET and not C#? As a VB.NET user for some time I never found it confusing. I found it more confusing that C# didn't support it. I'd be interested to hear a poll of VB.NET users.

HaloFour avatar May 06 '20 18:05 HaloFour

Anything that would be global would defeat a large number of use cases.

At the very least any sort of feature like this would need to be opt-in, either on a per-code-file or per-folder basis like the razor features that are mentioned.

At least this holds for how I use #defines if I use many of them and I see a lot of requests where this is used to change basic types, but while a compiler switch or other global configuration may work for applications I reckon it will create major headaches trying to extend this into libraries.

ericwj avatar May 06 '20 18:05 ericwj

// MyCommon.cs
namespace MyCommon {
	public using System;
	public using MyTypeA = Quux.MyTybe<A>;
	public using ...;
}

// Foo/Bar.cs
using MyCommon;
// MyTybeA ...
// ...

The above:

  1. Enables all use cases outlined in this proposal (you can put the public usings under an the top namespace)
  2. Enables use cases that inevitably be requested ("global using per folder", for example)
  3. Enables libraries to provide their own "using bundles"
  4. Is accessible and editable with all the tools of an IDE
  5. Seamlessly gels with any future features of using declarations

Personally I don't like the idea of "invisible" usings. They're invisible because usually one doesn't need to play with the build system often. And the core idea of reducing the number of lines of usings has so much more potential.

munael avatar Jul 24 '20 11:07 munael

@narfanar

I'm not sure that approach is any better. Those "using"s are just as invisible, now they're just packaged in other namespaces. Now importing a single namespace could also introduce any number of other namespaces or aliases which could introduce ambiguities or cause code to be silently reinterpreted. At least a project-level setting is explicitly determined by the developer.

HaloFour avatar Jul 24 '20 12:07 HaloFour

Something i forgot to mention in teh LDM meeting:

IDE has already put in a bunch of features to make 'usings' less relevant. For example, a common reason specified for having these usings automatically added is so that you can immediately just start using the types/extensions from it without needing to manually add the using first. However, IDE has a feature (which we've just switched to being 'on by default') whereby we will include all members from all namespaces in teh completion list, and will add the using for you automatically if you commit it.

This makes it easy to browse and find things (i.e. you can just type Xml or File or Stream) and then add what you need for that file.

As such, an empty file is a totally reasonable place to start using C#. With top level statements you can have that, and just type what you want (adding imports as necessary for the things you use).

CyrusNajmabadi avatar Sep 28 '20 18:09 CyrusNajmabadi

I strongly dislike the idea that a C# source file will suddenly behave differently based on what compiler options are specified (save checked/unchecked). It's already the case that examples of code on the web often leave out using statements - making the code ambiguous to the readers if types in the code exist in more than once namespace. Further encouraging people to leave out usings because they are now hidden behind compiler flags seems like a step backwards.

In any modern IDE it entirely takes care of the usings section for you- adding usings as needed, offering to remove unnecessary ones.

MgSam avatar Sep 29 '20 02:09 MgSam

@MgSam - good points on the issues with making a build dependent on non-code based command line parameters.

That said, I would hope we do not lose sight of the fact that the "ideas" presented here (and the problems they are trying to solve) are still valid, e.g., trying to find some to option to provide typedef style functionality with a "global" using alias would be a great add (imo).

As mentioned above, language augments are always an option. I sort of like the option mentioned to apply global using statements to the XML project file - seems like a simple, safe solution, and begins to fit your idea of a modern IDE "taking care of it for you".

Maybe the powers that be can consider the options discussed here.

ritchiecarroll avatar Oct 06 '20 20:10 ritchiecarroll

I think I had some similar discussion a while ago: https://stackoverflow.com/questions/61813506/implicit-assembly-wide-using-directive/61813769#61813769

msedi avatar Oct 06 '20 21:10 msedi

Take a lesson from VB and please add this. There is only confusion if done wrong.

craigajohnson avatar Oct 08 '20 00:10 craigajohnson

This seems like a real step backwards and archaic. Please don't add this. What problems does this solve?

LeonG-ZA avatar Oct 13 '20 14:10 LeonG-ZA

Code readability (at the risk of introducing ambiguity if done badly). Adding System.Threading.Tasks, System.Linq, System.Collections.Generic to every file to use async/await or .FirstOrDefault or List<T> offers me nothing and I have dreams with Ctrl-. / Enter to resolve squigglies

craigajohnson avatar Oct 13 '20 14:10 craigajohnson

@LeonG-ZA at least with a global using alias, e.g., using StringMap = System.Collections.Generic.Dictionary<string, string>; you basically get a free typedef

ritchiecarroll avatar Oct 13 '20 23:10 ritchiecarroll

It makes the cognitive complexity a lot worse. Types matter. When scanning through code you can immediately know what Dictionary<string, string> is, but StringMap someone unfamiliar with the code will have to pause and hover over it to see what it actually is. The more typedefs there are the more you have to think what is happening under the covers. I've worked with C++ over 10 years ago and it was one of my Pet Peeves was that typedefs were misused like this. It makes sense in certain situations, but this definitely isn't one of them.

LeonG-ZA avatar Oct 14 '20 06:10 LeonG-ZA

Namespace wide type aliases are especially useful when using generic type arguments

I have hundreds occasions of IFoo{int}

What if I choose to use Guid or long instead? I have to a) change every occasion (search and replace = bad) or ... b) define an alias for IFoo{int} and use that alias in every file (not feasible for 100+ files) or ... c) define an typed interface ITypedFoo : IFoo{int} and make sure every class inherits this new interface as well so I can use it properly I went for c) but it's not an optimal solution. A namespace-wide alias would solve the whole situation properly without any effort on developer side.

CleanCodeX avatar Oct 22 '20 10:10 CleanCodeX

It makes the cognitive complexity a lot worse. Types matter. When scanning through code you can immediately know what Dictionary<string, string> is, but StringMap someone unfamiliar with the code will have to pause and hover over it to see what it actually is. The more typedefs there are the more you have to think what is happening under the covers. I've worked with C++ over 10 years ago and it was one of my Pet Peeves was that typedefs were misused like this. It makes sense in certain situations, but this definitely isn't one of them.

Isn't that encapsulation though? Hiding implementation details when appropriate. It's the responsibility of the programmer not to misuse features like typedef. Granted, I don't know why would you have StringMap, but I could have domain-specific UserToEmailMap defined that way, and the reader shouldn't need to know it's based on Dictionary. Instead, I end up writing wrapper classes.

jukkahyv avatar Oct 22 '20 11:10 jukkahyv