If you could make breaking changes to C#, what would you do?
-
Branching from the two posts starting here and in potential connection with this "issue" on the official GitHub for C#
Pretty much what the topic says. One I've seen mentioned before around here is:
- Reference types shouldn't be nullable by default, instead using the same nullability operator/monad as non-reference types currently do.
-
@dreikin Remove the bits of automatic type conversion that snuck in.
char
is notint
.
-
Remove the non-generic legacy cruft such as the
System.Collections
namespace (but not its child namespaces; just stuff likeStack
andHashtable
) andIEnumerable
(non-generic version).
-
-
Make more explicit that
throw e
resets an exception's stacktrace. -
Port the "suppressed" exceptions concept from Java, make sure a
using
block keeps them together -
Maybe allowing enums to be associated with operators or extension functions for conversion, though the way Java handles them might also be too heavy-handed.
-
-
Not really breaking, but allow dynamic types to have certain known properties for the convenience of code completion and compile time type checking. Perhaps have an optional generic?
-
Checked arithmetic by default. No point having it if the easy, error prone way is the one you get without doing anything
-
@jaloopa said in If you could make breaking changes to C#, what would you do?:
Checked arithmetic by default. No point having it if the easy, error prone way is the one you get without doing anything
Seconded.
On the same vein,
DateTimeOffset
should be the default struct for storing date and time info, and should be renamed toDateTime
.(The current
DateTime
should either be chucked in the bin or given an appropriately scary name so developers understand it's very prone to causing hard-to-find bugs.)
-
Remove the need to add
break;
to switch cases.
-
@coldandtired said in If you could make breaking changes to C#, what would you do?:
Remove the need to add
break;
to switch cases.But would it still disallow drop-through?
-
@jbert Yes. As it is, you add the line just to keep the compiler happy.
-
@coldandtired said in If you could make breaking changes to C#, what would you do?:
@jbert Yes. As it is, you add the line just to keep the compiler happy.
Essentially, they put the requirement in to make it look like they're maintaining backwards compatibility with C, even though the C rule for automatic fall-through is no longer there.
-
@coldandtired said in If you could make breaking changes to C#, what would you do?:
Remove the need to add
break;
to switch cases.I'd prefer this to remain in, as it helps code clarity.
@jbert said in If you could make breaking changes to C#, what would you do?:
- Make more explicit that
throw e
resets an exception's stacktrace.
I'd favour something a bit different:
throw;
andthrow e;
preserve the stack tracethrow e with reset;
resets the stack trace
- Make more explicit that
-
@coldandtired said in If you could make breaking changes to C#, what would you do?:
@jbert Yes. As it is, you add the line just to keep the compiler happy.
You still need
break
to disambiguate betweencase 1: case 2: stuff()
and
case 1: break; case 2: stuff()
In fact this case gets really confusing when
break
is otherwise omitted, since the former example looks like it would do nothing for case 1.
-
forcing enum items to have explicit values declared
-
No the best thing about C# is that code that is from 15 years ago can work with minimal effort.
Almost every small tool I created 10 years ago that was winforms still worked fine under .NET 4.5
-
@lucas1 said in If you could make breaking changes to C#, what would you do?:
No the best thing about C# is that code that is from 15 years ago can work with minimal effort.
Imagine you had a time machine, if it helps.
-
@fwd I personally am rather glad that isn't forced.
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
In fact this case gets really confusing when
break
is otherwise omitted, since the former example looks like it would do nothing for case 1.Not to me. I've always used it to group the same result together.
case 1: case 2: stuff(); // 1 or 2
case 1: break; case 2: stuff(); break;
If the first case is supposed to do nothing then it's only included to keep the compiler happy, unless you have a situation where you want some cases to do something, some to do nothing, and some to do whatever the default is. Is this common?
-
@dreikin This is the few things I would want Microsoft to leave alone, so I wouldn't use the time machine on this one.
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
IEnumerable (non-generic version).
Hey, I'm using that! In fact I often find the pattern of generic interface inheriting a non-generic one rather useful, particularly in
where
clauses on generics.Ooh, that's a good potential improvement - make unbound generic types valid in certain contexts, such as
is
checks andwhere
clauses.
-
@maciejasjmj I think modern versions do most of that but I agree in principle.
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
@coldandtired said in If you could make breaking changes to C#, what would you do?:
@jbert Yes. As it is, you add the line just to keep the compiler happy.
You still need
break
to disambiguate betweencase 1: case 2: stuff()
and
case 1: break; case 2: stuff()
In fact this case gets really confusing when
break
is otherwise omitted, since the former example looks like it would do nothing for case 1.If we're reinventing syntax, then how about:
case 1, 2 { stuff() }
and
case 1 { } case 2 { stuff() }
-
@pleegwat Nope fuck that. I think the stuff we have is good enough.
"Fall through until break" is better than explicit break IMO.
-
@pleegwat said in If you could make breaking changes to C#, what would you do?:
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
@coldandtired said in If you could make breaking changes to C#, what would you do?:
@jbert Yes. As it is, you add the line just to keep the compiler happy.
You still need
break
to disambiguate betweencase 1: case 2: stuff()
and
case 1: break; case 2: stuff()
In fact this case gets really confusing when
break
is otherwise omitted, since the former example looks like it would do nothing for case 1.If we're reinventing syntax, then how about:
case 1, 2 { stuff() }
and
case 1 { } case 2 { stuff() }
Why add the braces? Just do
case 1, 2: stuff();
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
Hey, I'm using that! In fact I often find the pattern of generic interface inheriting a non-generic one rather useful, particularly in
where
clauses on generics.How does that come in handy?
Ooh, that's a good potential improvement - make unbound generic types valid in certain contexts, such as
is
checks andwhere
clauses.Sorry, you totally lost me here. What do you mean by that?
-
@coldandtired said in If you could make breaking changes to C#, what would you do?:
the first case is supposed to do nothing then it's only included to keep the compiler happy, unless you have a situation where you want some cases to do something, some to do nothing, and some to do whatever the default is. Is this common?
If you're switching on an enum, I'd say you usually want to include all enum values even if for some of them you don't do anything. And without
break
you have no way to represent an empty switch case, and if you try to, it causes a fallthrough instead, which isn't consistent.
-
@maciejasjmj That is more of a logic problem than a language problem IMHO.
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
Hey, I'm using that! In fact I often find the pattern of generic interface inheriting a non-generic one rather useful, particularly in
where
clauses on generics.How does that come in handy?
Ooh, that's a good potential improvement - make unbound generic types valid in certain contexts, such as
is
checks andwhere
clauses.Sorry, you totally lost me here. What do you mean by that?
Something like
var foo = bar.Where(baz => baz is IEnumerable);
maybe?
-
@jbert said in If you could make breaking changes to C#, what would you do?:
though the way Java handles them might also be too heavy-handed.
Java is always heavy-handed, long-winded and bureaucratic. This is axiomatic.
-
@dreikin said in If you could make breaking changes to C#, what would you do?:
@pleegwat said in If you could make breaking changes to C#, what would you do?:
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
@coldandtired said in If you could make breaking changes to C#, what would you do?:
@jbert Yes. As it is, you add the line just to keep the compiler happy.
You still need
break
to disambiguate betweencase 1: case 2: stuff()
and
case 1: break; case 2: stuff()
In fact this case gets really confusing when
break
is otherwise omitted, since the former example looks like it would do nothing for case 1.If we're reinventing syntax, then how about:
case 1, 2 { stuff() }
and
case 1 { } case 2 { stuff() }
Why add the braces? Just do
case 1, 2: stuff();
Actually, for full consistency with other constructs, it should be:
case( 1, 2 ) { stuff(); }
At which point the braces still shouldn't be optional because control structures without braces are evil.
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
Sorry, you totally lost me here. What do you mean by that?
if (obj is IEnumerable<>)
to return true if obj implementsIEnumerable<T>
for anyT
. It's not as useful as a non-genericIEnumerable
since you can't really do much with that knowledge without further reflection, but it's better than theGetGenericTypeDefinition
dance.
-
@pleegwat said in If you could make breaking changes to C#, what would you do?:
because
control structures withoutbraces are evil.FTFY
There are basically three ways we've come up with to format places where a block might be used.
- The ALGOL style, with a
begin
/end
token pair surrounding the block. The C family uses braces for these. - The Modula style, where everything is a block, so there's no need for a
begin
token, and every block ends with anend
token. Notable languages using this today include Ruby and Lua. - The Python style, where everything is a block, and there's no
begin
orend
token because it's done with indentation.
Having worked with all three, I think I like Modula style best and ALGOL style the least.
- The ALGOL style, with a
-
@dkf said in If you could make breaking changes to C#, what would you do?:
This is axiomatic
Or as Java programmers would say it:
this.getAttributeSet().addAttribute(Adjectivizer(axiomFactory.getAxiom()).adjectivize())
-
Breaking changes? I suggest disallowing the form:
if (xyz) thing.Do_Something();
and forcing the braces to be provided:
if (xyz) { thing.Do_Something(); }
It makes things one heck of a lot easier for maintenance programmers. (Similarly for other control constructs.)
If that's too much to stomach, go the other way and allow
try
andusing
to not be braced if you're only putting a single statement inside.
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
@pleegwat said in If you could make breaking changes to C#, what would you do?:
because
control structures withoutbraces are evil.FTFY
There are basically three ways we've come up with to format places where a block might be used.
- The ALGOL style, with a
begin
/end
token pair surrounding the block. The C family uses braces for these. - The Modula style, where everything is a block, so there's no need for a
begin
token, and every block ends with anend
token. Notable languages using this today include Ruby and Lua. - The Python style, where everything is a block, and there's no
begin
orend
token because it's done with indentation.
Having worked with all three, I think I like Modula style best and ALGOL style the least.
Python style is my favorite and I'd like it if C# went that direction, but I figured that be so big a change people would suggest it wasn't really C# anymore.
- The ALGOL style, with a
-
@dkf I would fuck you with a rusty rake if that happened. Honestly it is a language that is normally done with an IDE so you know whether the conditional is valid in the context of the method.
-
@dreikin said in If you could make breaking changes to C#, what would you do?:
I'd like it if C# went that direction
It causes problems with pasting snippets of code. Now, most of the time it doesn't bother me too much, but sometimes it's just real annoying when one's IDE doesn't know which block you're pasting code at the end of (and can't know; it's genuinely ambiguous to the IDE at that point).
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
How does that come in handy?
Sometimes you don't care what's in the enumerable, you just care that you can enumerate it. If it's a method then it's fine because of type inference:
public int Count<T> (IEnumerable<T> enumerable)
But when it's a type, things get less fun:
public class EnumerableCounter<T> where T : IEnumerable<...oops?>
-
@dkf said in If you could make breaking changes to C#, what would you do?:
@dreikin said in If you could make breaking changes to C#, what would you do?:
I'd like it if C# went that direction
It causes problems with pasting snippets of code. Now, most of the time it doesn't bother me too much, but sometimes it's just real annoying when one's IDE doesn't know which block you're pasting code at the end of (and can't know; it's genuinely ambiguous to the IDE at that point).
Hm, yes, I hadn't thought of that before. Still seems like something not terribly hard to deal with - just hit Tab or Shift+Tab as needed on the selected block immediately after pasting.
Meh. Tradeoffs suck. Why can't there always just be one optimal solution?
-
@dreikin said in If you could make breaking changes to C#, what would you do?:
Hm, yes, I hadn't thought of that before. Still seems like something not terribly hard to deal with - just hit Tab or Shift+Tab as needed on the selected block immediately after pasting.
That's fine if you're pasting once. Trivial. If you're pasting the same thing (often a function call) in a bunch of places, it's much more annoying.
Why can't there always just be one optimal solution?
There is! Just do it my preferred way and the kitten doesn't have to die.
-
@maciejasjmj Seems like you could do that without it being a breaking change, although I'm not 100% sure.
-
@dkf said in If you could make breaking changes to C#, what would you do?:
allow try and using to not be braced if you're only putting a single statement inside.
I'm pretty sure
using
allows that already. Isn't stackingusing
s exploiting that?using (var ms = new MemoryStream()) using (var ms2 = new MemoryStream()) { //... }
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
But when it's a type, things get less fun:
public class EnumerableCounter<T> where T : IEnumerable<...oops?>
public class EnumerableCounter { int Count<T> (IEnumerable<T> thingToCount) { ... } }
???
-
@dkf said in If you could make breaking changes to C#, what would you do?:
If that's too much to stomach, go the other way and allow
try
andusing
to not be braced if you're only putting a single statement inside.using
already allows that
-
@maciejasjmj You are correct.
-
@raceprouk beat me to it. nice one.
-
@masonwheeler said in If you could make breaking changes to C#, what would you do?:
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
But when it's a type, things get less fun:
public class EnumerableCounter<T> where T : IEnumerable<...oops?>
public class EnumerableCounter { int Count<T> (IEnumerable<T> thingToCount) { ... } }
???
What if you want to take the enumerable as a constructor parameter?
Of course that's a minimal example - in real life it probably would be doing something more involved than counting, possibly keeping some internal state that would justify it being a class.
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
using (var ms = new MemoryStream())
using (var ms2 = new MemoryStream())In that case, and only that case, you can do this:
using( var ms = new MemoryStream(), ms2 = new MemoryStream() ) { }
That only works because the two types your creating inside the
using
are the same type. It would be nice if C# supported doing that with different types, although I'm sure there's some good reason it doesn't.
-
@blakeyrat I think the problem would be precedence.
-
@maciejasjmj said in If you could make breaking changes to C#, what would you do?:
What if you want to take the enumerable as a constructor parameter?
Then the class would have a generic
<T>
and the constructor would take anIEnumerable<T>
.