Projectwide using statements and opt-out of usings #1777
Replies: 34 comments 1 reply
-
IIRC the C# compiler is entirely unaware of the project file or its format. This proposal would change that, requiring the C# compiler to understand (and include within its specification) a separate XML file, taking a dependency on one specific build system. In my opinion, if C# were to adopt this functionality then it should do so in a manner similar to VB.NET which has always supported project-wide imports. It accepts the imports on the command line, e.g.:
This would, of course, be driven by the build system and stored in the project file, so the result would be the same as this proposal. But the implementation would be independent of the project files and any particular build system. |
Beta Was this translation helpful? Give feedback.
-
An interesting side effect of this would be that NuGet packages could automatically add imported namespaces project-wide by putting them in their .props. This would be more idiomatic csproj: <ItemGroup>
<ImportNamespace Include="System" />
<ImportNamespace Include="System.Collections" />
<ImportNamespace Include="System.Collections.Generic" />
</ItemGroup> |
Beta Was this translation helpful? Give feedback.
-
I updated my original post, as it seems it caused some confusion. To be clear, I wouldn't want to change the project system, neither the syntax; the original problem I would like to propose a solution to is that you always have to import the most basic namespaces, or in larger projects, the same namespaces over and over. This can cause problems even when refactoring, or when someone unintentionally refers to a different class in a different namespace because it was not available in the current context. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour: yes, I understand, but isn't that exactly the same as NuGet references and assembly references? The source files themselves don't know of these, but the language service and project system most certainly does. When you're This proposal wouldn't change any of these. It would just import additional namespaces to scopes already defined by files currently, through an additional mechanism (be it the barrel file solution, the project file or anythin else). |
Beta Was this translation helpful? Give feedback.
-
I'm not sure I understand the need for |
Beta Was this translation helpful? Give feedback.
-
The compiler only knows the project references because they are passed as command line arguments to the compiler. The compiler knows nothing about the project system or NuGet. The mechanism for how additional namespaces would be imported is important. It's not good enough for them to exist in an XML-based project file somewhere as the compiler doesn't read those files. You need something that the compiler could understand. That would be C# source, which could be covered by a new project-wide form of |
Beta Was this translation helpful? Give feedback.
-
Let me elaborate. Let's say you have a project structure with the following files, where the folders also describe the namespace for the file:
Given the above, you could either import namespaces from other assemblies or your current project in the above files, or create import files like so: namespace MyOrderProject.BusinessLogic
{
internal using System.Collections.Generic;
internal using Microsoft.EntityFrameworkCore;
internal using MyOrderProject.DataAccess;
internal using MyOrderProject.DataAccess.Entities;
// And so on
} Let's say you create the above file (anywhere in the project), the language service picks it up, and everywhere inside your project's MyOrderProject.BusinessLogic namespace, you can use the above namespaces without explicitly having to import them via |
Beta Was this translation helpful? Give feedback.
-
I think we're on the same page. What you're describing isn't unlike Also, if your proposal is specifically for namespace MyOrderProject.SomeOtherNamespace {
public class Foo : List<int> { } // is System.Collections.Generic already imported here?
} |
Beta Was this translation helpful? Give feedback.
-
@HaloFour: sticking to the example above, everything in the |
Beta Was this translation helpful? Give feedback.
-
Related #1696. Back then we discussed a similar feature on the Roslyn repo. |
Beta Was this translation helpful? Give feedback.
-
Thank you @eyalsk, I've found some others (namely #2044 on the Roslyn repo ) that were around, but found that the conversation should be started again. I don't feel that this necessarily have to be a "file referencing" or "project global" type of thing, I feel that a namespace internal import would be more appropriate to the language's design philosophies. In implementation, the namespaces have reference to other namespaces, which are ambient (in the given context), but explicit (as it is defined in the project, it is assembly-wide). |
Beta Was this translation helpful? Give feedback.
-
I would expect assembly-wide Also I prefer |
Beta Was this translation helpful? Give feedback.
-
Yes, if you put the above |
Beta Was this translation helpful? Give feedback.
-
A namespace isn't limited to an assembly though. If you put this in a DLL:
And I reference it in my project and declare my own class in that namespace, a namespace-wide So would an |
Beta Was this translation helpful? Give feedback.
-
I really don't like this, but all name ambiguities could be resolved by trying each occurrence again without the project-wide imports. |
Beta Was this translation helpful? Give feedback.
-
I just don't think that all of the complexities are all that necessary. You all of a sudden get into weird combinations of scoping and resolution rules with nested namespaces. Project-wide imports that are established via command line argument seem sufficient to me, and at least we have another major language for precedence to determine if it creates weird situations. Named namespace bundles also sound interesting to me. Maybe I just don't think it's that big of a deal. Visual Studio writes the majority of the |
Beta Was this translation helpful? Give feedback.
-
@jnm2 Don't like the proposal or? Personally, I want something like this but still can't figure what's the best way to go about it, don't think this proposal or any of the ones I linked are good enough for me. |
Beta Was this translation helpful? Give feedback.
-
@eyalsk I don't like the following suggestion in my comment there. |
Beta Was this translation helpful? Give feedback.
-
I think the main cause of this feature would be to increase productivity. Obviously, if something in the flow becomes counterproductive, it's not worth it. I think the
One obvious disadvantage I can think of is:
I can't seem to conjure up any more pros/cons, but there are some things we haven't discussed yet, like:
Thanks everyone! |
Beta Was this translation helpful? Give feedback.
-
What we could do is have a using ThirdParty.UI.Button; // normal using statement
using static ThirdParty.UI.Button.Options; // using enum
using internal ThirdParty.UI; // opt-in using all the other namespaces included in the ThirdParty.UI namespace |
Beta Was this translation helpful? Give feedback.
-
I don't think this is true. Adding an For example - I add an The change that broke the code is nowhere near the code that broke. Worse, there's no relationship between the two that can be used to identify the problem. Another example - someone adds an |
Beta Was this translation helpful? Give feedback.
-
@theunrepentantgeek: Yes, I guess you're right about possibly breaking other code. What could be done though is that considering those existing files were building correctly before, that the file's own using statements always have precedence over ambient ones. Although I was talking about the feature only existing won't break any existing code, because not adding it to code won't break existing semantics. On the same note, you can still break existing code like this: consider you have A.cs which refers System.IO.File in namespace X. Then you create a class named File in namespace X, effectively breaking the other file. This is exactly the same example, only using Type names (which is type metadata, just as namespaces are). Not trying to be blunt though, but the compilation symbol example seems extremely far fetched to me, it could happen in any given scenario anytime, regardless of what feature's we're talking about here. Obviously, this is a language feature, requires a compiler version setting, but other than that I think that would be very hard to reproduce, and even then the reproduction would be quite forced, not realistic. And there is exact relationship between the colliding symbols, the compiler can show the error as pointing to the relevant files which ambiently import the colliding type. Similarly, the last point of using a shared source file has the same problems today regardless of this feature. |
Beta Was this translation helpful? Give feedback.
-
@yugabe So you mean if I wrote some code sample before this feature was added, then copy and pasted that same exact code into a new file after the update was made, that that code will behave differently and not work? I hope you can see why that's an issue. The code should work the same way every time, it shouldn't depend on when it was written. Even still, with that solution, I still have no control over the namespaces that are being added, and all the issues I already mentioned still apply.
The difference here is that I create that class, meaning I can delete that class or rename it if need be. And if it was created by a third party, I can choose which one I want to use in my using statement, and which one I will have to specify by it's full name, and I will use the one I'm using more often in the using statement, most likely. With your idea, if someone creates that File in namespace X, I can't rename it or delete it, and I could be forced to have it in my using statement without my control. Then someone else could have System.IO.File in their internal namespaces as well, so now I'm specifying both their full names each time, and I am getting zero benefit. The better idea is to have some There is still the issue of namespaces from using internal System.IO
using internal Namespace.X This could still cause ambiguity, but it's in a much better spot because I can control which one gives me the ambient namespaces. |
Beta Was this translation helpful? Give feedback.
-
@AustinBryan: no, that's exactly not what I am saying. Previous code won't break no matter what. Code written previously will still work, even if you import ambient namespaces in other files. |
Beta Was this translation helpful? Give feedback.
-
@yugabe You're saying that code that was working before will still work now, correct? If that's the case, then I could copy the code that worked before this update was published, and publish it into a brand new file. Since this file is new, it will be using the new update. This means that the there is a chance that the pasted code won't work. Do you not see how that is a problem? The code should always do the same thing. We shouldn't have a language where the code does different things depending on when that file was created. Also, what happens if you copy and pasted the file, provided the file was working before the update? Or someone installs the file or program? Yes, the original code won't break, but any duplications of the same exact code might and that is very problematic. |
Beta Was this translation helpful? Give feedback.
-
I believe any existing code will still work, this won't be a breaking change of any kind, even if you copy-pasted some legacy code into a totally new project or something like that. If I'm wrong, please, feel free to prove me wrong, I'll be happy to admit it. I'm gonna publish a public repo to try and demonstrate in the coming days. I don't want to keep running in circles about this. If you're saying that this is a breaking change, I don't think we are talking about the same thing. Until then, please let's just put the "old code's not gonna work" topic on ice, we'll get back to it later, if that's all right with you. Thank you. |
Beta Was this translation helpful? Give feedback.
-
Well thanks for everyone for your contributions and your patience in the last week. I started working on a repository with sample code but couldn't create a meaningful codebase to share after 3 tries (ninjas, courses and collections :)). I'm gonna go ahead and share another idea that could resolve all of the problems you outlined before. If you define a set of namespaces in a new type, which I'll call [File("\MyProject\Imports\SystemImports.cs")]
namespace MyProject.Imports
{
public imports SystemImports
{
using System;
using static System.Console;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
}
} This imports type is similar to other types (it can be enclosed in namespaces, has visibility modifiers, the default should be These [File("\MyProject\Imports\EntityImports.cs")]
namespace MyProject.Imports
{
internal imports EntityImports : SystemImports
{
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
}
} Using these [File("\MyProject\Data\Entities\MyEntity.cs")]
using imports MyProject.Imports.EntityImports;
namespace MyProject.Data.Entities
{
public class MyEntity
{
public int Id { get; set; }
public int? ParentEntityId { get; set; }
public MyEntity Parent { get; set; }
public ICollection<MyEntity> Children { get; set; }
}
} For ambiguity issues: I think the above should work exactly as if you used the namespaces themselves, but only a shorthand for using all of them instead of explicitly stating each and every one. This way backwards compatibility would be achieved by a compilation step which only outboxes the namespaces from the import objects into the file. One question is whether the type itself should be emitted to the IL output or not. If not, you'll only be able to use these constructs as source code, and won't be able to reference them cross-assembly. If this is the case, only internal kinds of imports should be supported. What do you guys think? |
Beta Was this translation helpful? Give feedback.
-
@yugabe You went from introducing a project-wide namespaces which should have a really simple solution to a very complex and expensive solution. Personally, I'd be happy with what @HaloFour suggested before but unlike VB.NET I think that these global namespaces should be imported explicitly per file which should be enough for most people. |
Beta Was this translation helpful? Give feedback.
-
Even a global declaration and explicit import would require modification of the compilation pipeline AND the language, no? Is it really that much easier to implement than what I outlined above? I think this would have the benefit of being a lot more versatile and explicit. The global declaration solution would solve the problem, but falling back to the "legacy" solution would require a lot of work. I'd be happy either way, my main problem is you cannot use any form of indirection to import a bunch of namespaces at once as you cannot bundle them. |
Beta Was this translation helpful? Give feedback.
-
TL;DR: it seems my original post was confusing, I'm gonna shorten it down here with less speculation:
It would greatly improve productivity for developers if they wouldn't need to import common namespaces in every file for a given project. The most versatile approach seems to be something along the lines of (
internal using
syntax is proposed):To be clear, I would be very happy with the above, but in my original post I outlined two additional - in my opinion inferior - proposals.
Original post as follows:
I would like to propose that in order to shorten boilerplate code and increase productivity (for not needing to look up namespaces for common types), there could be a mechanism to import namespaces and static contexts project-wide or namespace-wide, so that commonly used namespaces can be imported once only.
There could be three ways to tackle this I think:
Having a dedicated imports file, which only contains the usings (it should be validated for this subset of the language only). You could either import them in the *proj files or in the individual files.
Importing inside of a namespace could import those usings into the namespace across all source files, not just the current file. This actually invalidates points no. 1 and 2, as there could be any way for developers to organize namespace-wide and global (project-wide) usings in a project. To not collide with current implementations, a new syntax could be used to import namespaces into namespaces, for this I would propose the currently reserved keyword-pair:
internal using
.Either way, there has to be an opt-out logic to be able to avoid explicit collisions.
The benefits of this would be the developer wouldn't have to import commonly imported namespaces quite as often (such as System.Collections.Generic) and would reduce the time to look up extensions which are always hard to find without fully typing either the namespace or the name of the extension method and importing it via the lightbulb.
Beta Was this translation helpful? Give feedback.
All reactions