dotnet / csharplang

The official repo for the design of the C# programming language
11.61k stars 1.03k forks source link

Proposal: File scoped namespaces #137

Open bomzj opened 7 years ago

bomzj commented 7 years ago

Spec: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md

How it looks now:

using System;

namespace Company.Project
{
    public class Product
    {        
        ...
    }
}

Why do we need to nest our class definition in namespace ? Why not to remove extra nesting ? Isn't that better ?

namespace Company.Project
using System;

public class Product
{ 
    ...
}

LDM history:

scottdorman commented 7 years ago

Well, one reason is that namespaces can be nested. thereby creating additional scopes. For example, your code

using System;

namespace Company.Project
{
    public class Product
    {        
        ...
    }
}

could also be written as

using System;

namespace Company
{
    namespace Project
    {
        public class Product
        {
        }
    }
}

In both cases the full type name, including the namespace, becomes Company.Project.Product.

Also, using directives can be either outside the namespace, as in your example, or inside the namespace, which changes the scoping of that using directive.

bondsbw commented 7 years ago

Assuming the proposal does not remove the ability to nest namespaces (which would break all existing C# code), this could improve indentation for the vast majority of code.

114 has similar goals.

scottdorman commented 7 years ago

@bondsbw So you're saying that if this proposal were implemented we'd have 3 different ways to write the same scoped namespace?

using System;

namespace Company.Project
{
    public class Product
    {        
        ...
    }
}

or

using System;

namespace Company
{
    namespace Project
    {
        public class Product
        {
        }
    }
}

or

namespace Company.Project
using System;

public class Product
{ 
    ...
}

Would this new variation of the namespace directive support nesting, and if so, how? Would this be an implicit scope such that when I encounter the next namespace directive in the file it creates a nested namespace? That wouldn't work because I can have multiple top-level namespaces in a single file (never mind the question of whether or not that's a good idea, it's possible today and I've seen code that uses that ability, which would then break).

For example, if I wrote

namespace Company
using System;

namespace Project
public class Product
{ 
    ...
}

Does this compile Product to be Company.Project.Product or Project.Product?

I also noticed that the using directive is in a different spot in the original examples. In the first (what we have today in C#) it's outside the namespace. In the proposed code, it would be scoped to be inside the namespace (based on it's placement after the namespace directive). So, the original proposed code

namespace Company.Project
using System;

public class Product
{ 
    ...
}

would be equivalent to

namespace Company.Project
{
   using System;

   public class Product
   {  
       ...
   }
}
bomzj commented 7 years ago

Weird idea of using multiple namespaces in one file which should define one entity (class). I don't remember any sample where I need to have several namespaces in one file or nesting them which produces mass of indentation as result hard to read such code.

scottdorman commented 7 years ago

@bomzj I never said that multiple top-level namespaces in one file was a good idea, only that we can do it now and I've seen code files that take advantage of that. Having multiple nested namespaces in one file is also something we can do now and has completely unambiguous nesting semantics.

bondsbw commented 7 years ago

@bondsbw So you're saying that if this proposal were implemented we'd have 3 different ways to write the same scoped namespace?

Yes. Proposals to break backward compatibility are DOA except in the most minor cases. It would be a huge break to remove the block syntax.

bbarry commented 7 years ago

My vote would be: allow a file to allow up to 1 file level namespace directive per file and that it must come before any container namespace directives or type declarations.

Differentiate between a "file level namespace directive" and a "container namespace directive" by the fact that a file level one would not have a body and must have a semi:

namespace_declaration
    : file_namespace_declaration
    | container_namespace_declaration
    ;

file_namespace_declaration
    : 'namespace' qualified_identifier ';'
    ;

container_namespace_declaration
    : 'namespace' qualified_identifier namespace_body ';'?
    ;

Such a namespace directive would act as if it was a container namespace containing the rest of the file.

scottdorman commented 7 years ago

@bondsbw Yes, proposals which break back compatibly are DOA. I still don't see how this change provides any major benefit other than not having a level of indenting, which is fairly inconsequential.

@bbarry So you're saying that we would have a new namespace <identifier>; directive that would have to be at the top of the file? So, in the example we've been using here, namespace Company.Product; would have to be the first line and I can only have one of them? This would prevent me from defining more than one top-level namespace per file and also prevent me from having using directives appear outside the namespace declaration.

bbarry commented 7 years ago

No you could have using directives both above and below a file namespace directive. But you cannot have multiple file namespace directives and if you have 1, you cannot have container namespace directives or type declarations before the file namespace directive. using directives before the file namespace directive would be treated as outside and ones after, inside.

in examples:

ex1: (valid C# today)

using System;
namespace Company.Project
{
    using System.Collections;
    public class Product { }
}

or

ex2: (valid C# today)

using System;
namespace Company
{
    using System.Collections;
    namespace Project
    {
        public class Product { }
    }
}

or

ex3: (proposed, equal semantics to ex1)

using System;
namespace Company.Project;
using System.Collections;
public class Product { }

or

ex3: (proposed, equal semantics to ex2)

using System;
namespace Company;
using System.Collections;
namespace Project
{
    public class Product { }
}

but not:

ex5 (admittedly an arbitrary example, but the rational would be that if you are using both a container namespace and a file namespace it is because you have more than one container in your file or you require the semantics of ex2):

using System;
namespace Company;
namespace Project;
using System.Collections;
public class Product { }

and not:

ex6 (again arbitrary; here I am explicitly denying this as in scope in this proposal to consider it potentially for the future):

using System;
namespace Company 
{
    namespace Project;
    using System.Collections;
    public class Product { }
}

and not:

ex7 (avoiding a pit of failure due to overly complex files):

using System;
namespace Whatever {};
namespace Company.Project;
using System.Collections;
public class Product { }

and not:

ex8 (again avoiding complexity):

using System;
delegate void MyFunc(string s); //or any other type decl
namespace Company.Project;
using System.Collections;
public class Product { }
jnm2 commented 7 years ago

I was hoping this would make it over from /roslyn.

Putting the namespace in the class name appeals to me the most since I have never, ever defined more than one namespace per file. It would be great if that worked for partial classes too:

// File MyClass.cs
partial class MyNamespace.MyClass { }
// File MyClass.NestedType.cs
class MyNamespace.MyClass.NestedType { }

However, the namespace with an implied scope extending to the end of the parent scope would be an acceptable compromise. There is no possible ambiguity.

@scottdorman About ambiguity: just like the using proposal, the scope is not ambiguous. It always extends to the very end of the parent scope, the same behavior that the scope of a declared variable would have in that position. I don't care for the using syntax, but the same principle applied to namespace would be a huge benefit because four nines of the time people have a single namespace per file.

scottdorman commented 7 years ago

I agree, the vast majority of the time we only have one namespace per file and only have one class per file. However, that's not always the case and it's certainly not enforced by the compiler and/or IDE. (I've worked in languages that enforced this, and it got to be a real pain. That's not to say there may be good ways to actually do this where it doesn't get in your way more often than not, though.)

The ambiguity about what the scope actually is comes about if multiple namespace x; directives are allowed in a single file. Does the outermost directive define a scope such that the next directive is nested? If so, that is more limiting than what we have now where I can define the scopes however I want/need.

I'm not a big fan of having the namespace be part of the class name at all. First, it requires changing what constitutes a valid class identifier, but more importantly I think it introduces a lot of redundancy as far as namespaces go. Would the using directives just appear before the class declaration? How would you scope them to be contained within the namespace?

Given @bbarry's earlier examples, I still find this

using System;
namespace Company
{
    using System.Collections;
    namespace Project
    {
        public class Product { }
    }
}

to be considerably more readable than

using System;
namespace Company;
using System.Collections;
namespace Project
{
    public class Product { }
}

Especially since, at least to me, all this is really doing is letting me skip two curly braces and some indenting, I don't see the benefits. (I have the same issue, among others, with the implicitly scoped using statements as well.) I'd much rather see language features that make the language easier to read (which this doesn't, in my opinion) and reduce my ability to write code that can have unexpected/unintended side-effects than ones that make it harder to read and introduce more places for ambiguity.

jnm2 commented 7 years ago

The ambiguity about what the scope actually is comes about if multiple namespace x; directives are allowed in a single file.

I don't see an ambiguity. Same with the using thing:

using (a);
// Invisible { for a

// Still using a even though no indent

using (b);
// Invisible { for b

// Still using a and b even though no indent

// Invisible } for b
// Invisible } for a
namespace a;
// Invisible { for a

// Namespace here is "a" even though no indent

namespace b;
// Invisible { for b

// Namespace here is "a.b" even though no indent

// Invisible } for b
// Invisible } for a

I agree with you that this would not be as flexible for multi-namespace files, but

  1. the same goes for the using and there is an easy workaround if you need to be more specific about scope: go back to using { and } for that file
  2. We're honestly talking 0.01% of the time. Let's optimize for the common scenarios that affect everyone every day. The same goes for multiple levels of using nesting. If it's too complex, choose not to do it and use { and } instead. But don't punish the almost universal case of single using block, single namespace.
bomzj commented 7 years ago

Why not to use 2 types of declaring namespace ? The old approach with nesting where multiple namespaces are need and new one as single line for vast majority. One more loud thought whether it's possible to eliminate namespace keyword from *.cs files at all and map fully qualified class name to physical folder structure instead like java does ?

bondsbw commented 7 years ago

@bomzj I suggest opening up a separate proposal for the folder structure idea.

DavidArno commented 7 years ago

@scottdorman,

using System;
namespace Company;
using System.Collections;
namespace Project
{
    public class Product { }
}

wouldn't (or at least, shouldn't) be allowed under this proposal. By having two namespaces, you defeat the whole point of it. If you need multiple namespaces, then use the old syntax. For those 99.9% of times when only one namespace is used, then the nesting can be reduced down.

I'd actually take this proposal one step further and allow the removal of {} for the type too if only one type is needed in the file:

using System;
public class Company.Project.Product;

private readonly bool _someFlag;

public Product() => ...

etc
HaloFour commented 7 years ago

I like this idea but only when it's limited specifically so that namespace must the first directive and it is the only namespace declaration within that file. At that point it's similar to the Java package directive, except without all of the forced folder structure nonsense.

gordanr commented 7 years ago

@HaloFour why must be the first directive? Isn't it enough to be the only namespace declaration within that file?

I like this idea but only when it's limited specifically so that namespace must the first directive and it is the only namespace declaration within that file. At that point it's similar to the Java package directive, except without all of the forced folder structure nonsense.

iam3yal commented 7 years ago

@gordanr Probably but why would you put it elsewhere?

HaloFour commented 7 years ago

@gordanr

why must be the first directive? Isn't it enough to be the only namespace declaration within that file?

I think it helps the directive to stand out. Otherwise it might get lost after a long list of using directives and look like a source file with no namespace directive.

gordanr commented 7 years ago

@eyalsk It is not so important, but maybe someone wants to put using before namespace. @HaloFour Yes. If the using list is long, It's better to put namespace first. But for shorter list, I prefer using first. Not so important.

YaakovDavis commented 7 years ago

eliminate namespace keyword from *.cs files at all and map fully qualified class name to physical folder structure instead like java does

No need to import bad ideas from Java. In C# projects, you can arrange files freely without breaking code (or relying on automatic IDE refactorings which might or not work, depending on the your code state).

Also, In Java projects you typically have gazillion of namespaces, precisely because of this structure. No thanks, I don't want a C# mode that enables this messiness.

Regarding this proposal, I'd like to keep having the freedom to combine multiple namespaces in 1 file. I use it for related extension classes, where each of the classes is defined in a different namespace (the same namespace as of the class it extends).

gordanr commented 7 years ago

Can anyone see any conflict with partial classes?

Also, I am not sure is the proposal's name appropriate. I have no idea how, but probably can be better.

jnm2 commented 7 years ago

For all my files that have a single namespace (which is all of them so far, personal and work, including every file I've ever edited or read in an open source project), nothing would make me happier than this:

using System;

public class My.Namespace.ClassName
{

}

And I do mean nothing. :-)

bomzj commented 7 years ago

@jnm2

This might be good but Visual Studio does not do this automatically, so you have to amend it every time you create class.

jnm2 commented 7 years ago

@bomzj First things first. :-) If the language supports it, Visual Studio templates will be able to be created eventually.

(Speaking of which, for every console app I ever create, I add public static in front of the class, public in front of Main, usually delete the args parameter, and delete the usings. Otherwise, ReSharper lights everything up and complains that it doesn't match my code style settings, which is true. Plus it's totally easy to add usings as you type. I've been meaning to edit the template for myself. But this is another story.)

ig-sinicyn commented 7 years ago

@bomzj

Vote down. Placing using inside a namespace may result in breaking change for existing code.

Except for this the proposal has no benefit but adding one more subtle syntax for already existing feature. We have 8 (or nine?) ways to declare a property now. Isn't it enough? 😕

jnm2 commented 7 years ago

@ig-sinicyn I don't know if it deserves a down vote for those reasons. It's not a breaking change to any existing code. If you edit your code to start using the new syntax in the proposal, that's no more breaking than if you edit your code to put your usings inside.

Also, less indent and fewer curly braces to worry about are benefits for sure.

HaloFour commented 7 years ago

@ig-sinicyn

Given that this proposal requires explicitly using a new syntax which wasn't previously legal this wouldn't result in any breaking changes to existing code.

The proposal is indeed minor, but I think reduction of noise indentation is not a bad thing. For the vast majority of source files it's a wasted block of horizontal space, particularly given how rare it actually is to have more than one namespace (nested or otherwise) in a single source file.

ig-sinicyn commented 7 years ago

@HaloFour

Ok, let me reformulate in the following way:

  1. This proposal introduces change in type resolution logic that will be unexpected by most of C# developers.

  2. This proposal continues series of "just because we can" proposals. It has no real use-case behind it, it adds no benefit but saving two chars and it makes language more complex due to perl-style 'There's more than one way to do it' motto. It's all ok but c# is not perl. There's a lot of doomed languages filled with insane and controversal features that no one can explain or deal with. At the same time there are a very few languages with clean and sane design, obvious syntax and (almost) without hype-inspired design process. As for me there's no sense in implementing proposals that fails '-100 points' rule and will move c# into the first group:)

HaloFour commented 7 years ago

@ig-sinicyn

This proposal introduces change in type resolution logic that will be unexpected by most of C# developers.

That depends on how it is implemented. Those name resolution differences are largely academic anyway.

This proposal continues series of "just because we can" proposals. It has no real use-case behind it, it adds no benefit but saving two chars and it makes language more complex due to perl-style 'There's more than one way to do it' motto. It's all ok but c# is not perl.

2-4 chars for every single line within that namespace declaration. Horizontal real-estate otherwise completely wasted.

Having multiple ways of doing something is not inherently a bad thing. Having a simpler syntax to support the majority case is a good thing for both productivity and readability. Lambdas, auto-implemented properties, etc.

Not every language change has to be ground breaking.

There's a lot of doomed languages filled with insane and controversal features that no one can explain or deal with.

There's nothing insane or controversial here. This is nearly identical syntax to Java. Supporting said syntax won't "doom" C#.

I won't shed a tear if this isn't implemented.

jnm2 commented 7 years ago

This proposal continues series of "just because we can" proposals. It has no real use-case behind it

Neither statement is true. I've wished for this independently from the proposal for over four years precisely because of the use-case.

bondsbw commented 7 years ago

At the same time there are a very few languages with clean and sane design

Providing a cleaner design alternative for C# would then be welcome, no?

ig-sinicyn commented 7 years ago

Providing a cleaner design alternative for C# would then be welcome, no?

That's easy. Just stop implementing anything that is proposed and return to "big feature" design style that worked great for almost fifteen years.

Look, only this release introduced features that looks strange and will not (and may not) be fixed in a future. Leaking out vars, tuples that are just a stub until records will be implemented, pattern matching that fails on tuples or nullables (okay, this one may be fixed in future), local functions that may silently capture and modify local variables, throw expressions that are misused like

var a = b ?? throw new ArgumentNullException();

(and it is proposed by Visual Studio)

Next release will introduce another ones. And some time after we will have another perl or javascript - a huge collection of gothcas and design mistakes no one likes to use.

DavidArno commented 7 years ago

@ig-sinicyn,

Out of curiosity, what do you view those "big features" as being? You have expressed your criticism of features that have been added to C# 7, but I can't find any suggestions from you as to what features should have been implemented instead.

I agree that leaking out vars is politics winning out over good design, but I disagree with the rest of your comments. Tuples are incredibly useful and will remain so even when records are implemented. The team made clear that pattern matching is far from complete and lots more will be added to that feature in future. Local functions can be used for closures, just as lambdas can, this again is a useful feature at times. And your comment about misusing throw expressions make no sense as your code example shows exactly how they are supposed to be used.

We don't all use all the new features, but that doesn't make them bad choices or design mistakes. For me, ref returns are an utterly useless feature that I'll never likely use. But they are incredibly useful for others. C# is a language used by many folk for many different reasons, so it will always have features added that you don't see a need for.

There are big features (likely) coming to the language: records, the shapes idea that Mads recently wrote about, more pattern matching features, discriminated unions, code generators ... and that's just the things I care about. So as per my original question, what is it that you are looking forward to?

ig-sinicyn commented 7 years ago

@DavidArno

Out of curiosity, what do you view those "big features" as being?

It's pretty obvious. Currently there are (at least) three "game changer" things in process

Also, there're some ideas for next big things that are not designed completely yet. To name a few:

All of these obviously will require some syntax changes and will introduce a new language constructs. So it looks like a good idea to focus on them instead of adding small but not-so-useful features that may become blockers in future.

Instead of this current c# design process looks pretty random. There are tuples that will be superceded by records almost immediately as records will be released - there's no scenario where tuples will be better than records. There 's pattern matching, but it is unfinished and it conflicts with existing language constructs (tuples vs recursive patterns). There are local functions that are not so useful compared with complexity they do add into refactorings / compiler. As a result there are bugs that will ship into this release. Can you imagine it with pre-roslyn era compiler?

Can these issues be explained by lack of time only? Nope. Its much more about priorities. There was time spent on throw expression that has no use cases for it in current release (throw as expr was planned as a part of match expression that did not fit into c# 7 timeframe). There was a time spent on designing weird things such as custom default struct ctors or json literals, ability to write const x = 2, ability to add attributes into method body and so on.

Are these much more important than having bug-free releases without partially undone features? Really?

DavidArno commented 7 years ago

@ig-sinicyn,

Again you are confusing "has no use cases" with "I can't think of a use".

"there's no scenario where tuples will be better than records". Completely false. I have made use of tuples in many ways in my code to remove the need to create and use POCOs. To then complicate my code once more by stripping out those tuples and to use records instead would be ridiculous. You may not be able to think of such scenarios, but I can.

"There was time spent on throw expression that has no use cases for it in current release". Completely false. I am using throw expressions to simplify methods along the lines of:

public T1 Case1()
{
    if (Case == Case1) { return _value1; }
    throw new InvalidCaseException(Case1, Case);
}

to:

public T1 Case1() => Case == Case1 ? _value1 :  throw new InvalidCaseException(Case1, Case);

Again, you may not write code like this and so are unable to conceive of use cases for throw expressions. I do think that way though and so can.

I love little noise-reducing additions to the language, such as being able to write const x = 2, or this feature which removes a whole layer of indentation. You see them as less important. Being able to put attributes inside a method would be awesome, I use a bunch of analyzers that I wrote, including one that treats reassigning a variable as an error, unless it's marked as [Mutable]. Currently this has to be put against the method:

[Mutable("var1")]
public void F()
{
    var var1 = 1;
    var1 = 2;
}

Being able to put it against the declaration would improve the code:

public void F()
{
    [Mutable] var var1 = 1;
    var1 = 2;
}

Again, you may not see a use for this feature. I do. Likewise there are potentially features you'd like to see that I'm indifferent to.

Are these much more important than having bug-free releases without partially undone features?

Yes, they are, in my view. Sure, we might have had less bugs before the Roslyn days. But we also had a 2+ year release cycle for language changes and a closed source with no easy way for the community and the team to exchange ideas on the future of the language prior to those releases. The world has changed. C# 7.1 will likely be released in a few months with a VS2017 Update 1 and it will bring bug fixes, but also more (sometimes partially implemented) features.

Roslyn and an "in the open development" attitude gets us more community involvement, open source, more rapid, small step, language releases and more clarity over what's likely to be coming next. To my mind at least, these benefits easily outweigh a few bugs and no more "big bang" features.

You clearly feel differently, and I respect that. What I can't respect though are your (absolutely false) statements of absolute around features you do not see a need for.

bondsbw commented 7 years ago

@ig-sinicyn Back to the subject at hand, I fail to see how this feature compares with the things you think are bad/problematic design choices. It is about shaving off four spaces from the left margin of nearly every .cs file.

gordanr commented 7 years ago

Regarding shaving off four spaces from the left margin, it would be interesting to see what is the percentage of single namespace C# files. More then 90%?

gordanr commented 7 years ago

@ig-sinicyn, I agree that there are "game changer" things, but there are also small cute changes that make life easier.

When I first saw Expression-bodied function members, I thougt that C# team run out of ideas.

public string GetFullName() => FirstName + " " + LastName;

Now, that's my favorite small feature.

jnm2 commented 7 years ago

@gordanr Anecdotally, 100% for me. Someone should write a Roslyn scraper for GitHub...

gordanr commented 7 years ago

I think this is important for success of this nice proposal.

Could someone suggest better title than 'make namespace not contain class definition'? I would say that namespace in introductory example does contain class definition. More precisely, namespace need not contain classes at all. Word class should not be in the title. Do you agree?

It is more about reduction of noise indentation, braces, ...

gulshan commented 7 years ago

I like what @HaloFour has suggested-

I like this idea but only when it's limited specifically so that namespace must the first directive and it is the only namespace declaration within that file.

But I would like to allow other old-style {} namespace declarations within the file. In that case, those namespaces would be treated as nested namespaces to the header/default namespace declared at the top.

I also suggest the title for this issue- Default Namespace declaration at the top of file.

gordanr commented 7 years ago

Default Namespace declaration at the top of file It's a better name, but I feel it could be even much better. Do we need to introduce new term "default namespace"? We already have two similar terms "global namespace" and "Visual Studio's default namespace".

Basically it is old, regular namespace, but with new notation.

bondsbw commented 7 years ago

It isn't so much a "default" as much as it is a scope. So perhaps File-Scoped Namespace Declaration is a better fit.

gordanr commented 7 years ago

Maybe it is better name. Is it OK name if we have the same "file-scoped" namespaces in different files?

MgSam commented 7 years ago

For reference, my original proposal for this in the Roslyn repo was here:

https://github.com/dotnet/roslyn/issues/595

gordanr commented 7 years ago

@MgSam Thanks for the original proposal. I was unaware of them.

gordanr commented 7 years ago

@bomzj How about changing proposal title to Proposal: File scoped namespaces?

bomzj commented 7 years ago

@gordanr I guess "Proposal: File scoped namespaces" is fine, do you want me to change the title ?

gordanr commented 7 years ago

@bomzj I think that would be nice. Thanks.