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: Project level using statements #2044

Closed
yufeih opened this issue Apr 16, 2015 · 16 comments
Closed

Proposal: Project level using statements #2044

yufeih opened this issue Apr 16, 2015 · 16 comments
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.

Comments

@yufeih
Copy link
Contributor

yufeih commented Apr 16, 2015

Problem

All C# code starts with a list of using statements. For a large project, it is not uncommon for each file to contain more than 20 lines of using statement at the top. When moving code around, a lot of time is wasted managing namespaces.

IDE can help import and remove unused namespaces, but the problem itself does go away.

I propose to have a project level using statements, because files within a project are likely to share a set of common namespaces. With that, using statement inside the code file is used more for conflict resolution.

Design

There are couple of ways to do it and I personally prefer the first approach because it integrates well with the new project file format.

  • Used a dedicated json file, or extend project.json.
{
    "namespaces": [
        "System",
        "System.Collections.Generic",
        "System.Linq"
    ]
}

project.json is a separate thing from Roslyn, so for Roslyn, this means csc.exe taking extra parameters just like how references are specified.

  • using global System.Collections.Generic inside a cs file, just like how global attributes are specified.
  • Automatically import all the namespaces of all referenced assemblies. Just an idea, this may create too much conflicts.
@SolalPirelli
Copy link

This means that to understand the meaning of any C# file, you'd need to take a look at the project file, or even at every file in the project with the global variant.

One specific problem I see with this, especially the global variant, is overload resolution of extension methods (as well as static methods with C# 6's using static). Adding one namespace to the list of globally imported namespaces could silently change the meaning of existing code because a newly-imported extension method is a better fit for some calls.

For instance:

// Extensions1.cs
namespace Extensions1
{
    public static class Extensions1
    {
        public static void DoStuff( this int n, object o ) { /*...*/ }
    }
}

// Extensions2.cs
namespace Extensions2
{
    public static class Extensions2
    {
        public static void DoStuff( this int n, int n2 ) { /*...*/ }
    }
}

// Program.cs
using Extensions1;

public class Program
{
    public static void Main( string[] args )
    {
        123.DoStuff( 456 );
    }
}

Adding a global using for Extensions2 would silently pick the extension method declared in that namespace instead of the one in Extensions1, changing the meaning of existing code.

@paulomorgado
Copy link

@yufeih, are you aware that you can declare using directives at the top of the compilation unit (usually a code file) and at the top of the namespace declaration body?

What you are looking for already exists and it's called Visual Basic

@yufeih
Copy link
Contributor Author

yufeih commented Apr 16, 2015

@SolalPirelli You already need the knowledge of assembly references in the project file to understand and resolve the type names, so this is probably not something new.

The global using should be a fallback when the namespace is not found in the cs file. In your example, because Program.cs has an explicit using Extensions1 statement, Extension1 is still picked for the extensions method.

@yufeih
Copy link
Contributor Author

yufeih commented Apr 16, 2015

@paulomorgado I think what I am trying to achieve is to reuse using statements across multiple code files, not multiple code blocks within a single file.

For instance: File1 and File2 are two classes implemented in two different cs files.

// File1.cs
using System;
using System.Linq;

public class File1 { /*  */ }
// File2.cs
using System;
using System.Linq;

public class File2 { /*  */ }

I'd like to simply write:

// File1.cs
public class File1 { /* Code that uses types under System and System.Linq  */ } 
// File2.cs
public class File2 { /* Code that uses types under System and System.Linq  */ } 
// project.json
{
    "namespaces": [
        "System",
        "System.Linq"
    ]
}

I am not a VB guy but in C# there is currently no way to do that.

@paulomorgado
Copy link

@yufeih, the differences between C# and VB exist because different people want different things. And it looks like you want to be a VB developer.

I like to have my using directives on the file and only the ones that are needed.

And I like to decide if I want the using directives inside or outside of the namespace declaration.

@jmarolf
Copy link
Contributor

jmarolf commented Apr 16, 2015

@paulomorgado is correct that VB already has this. You will see the following in almost all .vbproj files:

  <ItemGroup>
    <Import Include="Microsoft.VisualBasic" />
    <Import Include="System" />
    <Import Include="System.Collections" />
    <Import Include="System.Collections.Generic" />
    <Import Include="System.Data" />
    <Import Include="System.Diagnostics" />
    <Import Include="System.Linq" />
    <Import Include="System.Xml.Linq" />
    <Import Include="System.Threading.Tasks" />
  </ItemGroup>

These namespaces are included automatically for every file in the project. The question is: Why does VB have this and C# doesn't? The main reason is philosophical: VB and C# have different design philosophies. While not always true, C# likes to explicitly show the user what is happening (always showing which namespaces are being used at the top of the file) whereas VB is willing to hide things if it saves 90% of the users time or complexity (auto import common namespaces).

We would need to add the Imports functionality from VBC to CSC and plump all the options through the compiler. If someone feels strongly about this and we can all come to consensus about wanting it to happen, this seems like a reasonable thing to submit a pulI request for.

@alexpolozov
Copy link

I'm going to add one more item to this proposal: project-level type aliases, with a similar syntax. This is something that many people have been asking for years.

Something like this (borrowing the syntactic scope qualifier from attributes):

assembly:using Pair = System.Tuple<System.Int32, System.Int32>;

Alternative syntax:

global::using Pair = System.Tuple<System.Int32, System.Int32>;

Assuming that we have a C# syntax for it (as opposed to a metadata file, like in VB), both kinds of usings could go in a separate file (similar to how AssemblyInfo.cs is used currently).

@paulomorgado
Copy link

Exactly, @jmarolf. It's philosophical. Are we changing the philosophical?

@paulomorgado
Copy link

While you are at it, @apskim, why not proposing a project wide namespace for every type without the namespace declaration?

@JoshVarty
Copy link
Contributor

I personally prefer the explicit approach currently taken in C#.

Visual Studio's Ctr + . feature is really good at resolving namespaces quickly. Based on my experiences, I'd disagree that very much time is spent managing namespaces.

@jmarolf
Copy link
Contributor

jmarolf commented Apr 16, 2015

@paulomorgado if you want my opinion, probably not, but I don't speak for everyone. I would imagine that most C# developers feel it is simpler to have one source of truth for what namespaces are included in a file. I imagine most VB developers like not having to see boilerplate import statements at the top of their file. Lets have the discussion to see if this is a correct assumption.

@yufeih
Copy link
Contributor Author

yufeih commented Apr 17, 2015

@jmarolf I think for philosophy, it is more about giving people choice and let them decide for their own. This is like the var keyword, it is also not explicit and seems to be against the philosophy, but it is now gaining popularity.

The comparison is probably not fair because a lot of C# developers (like me) are not aware of such thing in VB. If C# has the same concept from day 1, they might get used to it.

@paulomorgado
Copy link

@yufeih, var is explicit. The type is inferred, but it's very explicit that a variable is being declared and will shadow a field with the same name.

@bbarry
Copy link

bbarry commented Apr 17, 2015

I find this feature in VB very irritating. We used to have some utility classes we could include in some of our old vb projects that defined internal classes we didn't want to expose to users. At some point someone thought it was a good idea to switch to project level imports for an ever growing number of such imports. For a few months our builds broke here and there where someone moved an import to project level but didn't update every project that used the file.

@whoisj
Copy link

whoisj commented May 26, 2015

I'd rather see a feature by where a namespace automatically includes other namespaces. Not sure how it would semantically work, but something like:

[File1.cs]:

namespace MyNamespace
{
    include System;
    include System.IO;

    // ... code
}

In this way, any use of the namespace in another file would automatically include the namespace included files.

[File2.cs]:

namespace MyNamespace
{
    class Foo
    {
        bool UselessMethod()
        {
            // System.IO.File can be used because MyNamespace always 
            // includes the System.IO namespace as defined in File1.cs
            return File.Exists("foo.bar"); 
        }
    }
}

The suggestion by @apskim is also fairly good, though I'd like to add a twist to it:

assembly:using MyNamespace += System;
assembly:using MyNamespace += System.IO;

@gafter
Copy link
Member

gafter commented Sep 13, 2015

We would not do this in C# because the name lookup rules would then depend on things outside the language (or, to put it another way, it would extend the language to the contents of other "files" and we don't want to add the format of those other files to the language specification).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.
Projects
None yet
Development

No branches or pull requests

9 participants