csharplang
csharplang copied to clipboard
[Proposal] Add ability to declare global usings for namespaces, types and aliases by using a command line switch
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
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.
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.
@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.
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)
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.
My opinion: I really like _Imports.razor from ASP.NET Core Razor framework; I'd like to see something similar for C# as well. :)
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
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.
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)
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
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.
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.
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.
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.
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
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.
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.
// MyCommon.cs
namespace MyCommon {
public using System;
public using MyTypeA = Quux.MyTybe<A>;
public using ...;
}
// Foo/Bar.cs
using MyCommon;
// MyTybeA ...
// ...
The above:
- Enables all use cases outlined in this proposal (you can put the
public usings under an the top namespace) - Enables use cases that inevitably be requested ("global using per folder", for example)
- Enables libraries to provide their own "
usingbundles" - Is accessible and editable with all the tools of an IDE
- Seamlessly gels with any future features of
usingdeclarations
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.
@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.
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).
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 - 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.
I think I had some similar discussion a while ago: https://stackoverflow.com/questions/61813506/implicit-assembly-wide-using-directive/61813769#61813769
Take a lesson from VB and please add this. There is only confusion if done wrong.
This seems like a real step backwards and archaic. Please don't add this. What problems does this solve?
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
@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
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.
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.
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.