4.5 KiB
C# Language Design Meeting for March 25, 2020
Agenda
-
Questions around the new
nint
type -
Target-typed new
Discussion
Issue #3259
LangVersion
THe question is what the behavior of the compiler should be when seeing an nint
type in langversion
C# 8. Our convention is that the compiler never preserves
old behavior for older language versions. For instance, we do not preserve the
code for older code generation strategies and switch to that with the language
version flag. Instead, langversion
is meant to be guard rails, providing
diagnostics when features are used that aren't available in older versions of the
language.
There are a few options we could take.
-
Make an exception for
nint
, allowing them to be seen and compiled like anIntPtr
inlangversion
C# 8. -
Make a wider divergence between
nint
andIntPtr
. Adding amodreq
to the emittedIntPtr
type would make them effectively unusable by older language versions and other languages. -
Preserve the behavior, as long as no new semantics are used. For instance, using the arithmetic operators on
nint
and onIntPtr
have different semantics. It would be an error to use any of these operators in older language versions.
Conclusion
We think (3) is the best balance.
IntPtr
and nint
operators
We have two proposals:
-
Remove built-in identity conversions between native integers and underlying types and add explicit conversions.
-
Remove
nint
operators when using theIntPtr
type
Conclusion
(1) is a little too harsh. Let's do (2).
Behavior of constant folding
The concern is platform dependence.
In the following example
const nint m = int.MaxValue;
const nint u1 = unchecked(m + 1);
nint u2 = unchecked(m + 1);
if the machine is 32-bit, then the result overflows. If the machine is 64-bit, it does not.
While it's possible in the existing language to produce constant-folded values which are undefined, we don't think that behavior is desirable for nint.
The main contention is what to do in a checked
context if we know the value will overflow
32-bits. We could either produce an error, saying that this will overflow on some platforms,
or produce a warning and push the calculation to runtime, warning that the calculation may
overflow at runtime (and produce an exception).
Conclusion
Whenever we can safely produce a constant value under 32-bits, we do constant folding. Otherwise,
the result is non-constant, and under checked
, the code produces a warning and the result
is non-constant.
Interfaces on nint
?
Should interfaces on IntPtr
and nint
match? Or should nint
only accept a certain set of
compiler-validated interfaces on IntPtr
?
Conclusion
We trust that interfaces will only be added to IntPtr
with recognition that those interfaces
also affect nint
. We'll make all interfaces on IntPtr
available on nint
, with IntPtr
occurrences substituted for nint
.
Target-typed new
https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md
Clarification about library evolution: if a user uses new()
, adding a constructor to a type
can produce an ambiguity. Similarly, if a method is called with new()
that can produce an
ambiguity if more overloads of that method is added. This is analogous with null
or default
,
which can convert to many different types and can produce ambiguity.
The spec currently specifies that there are a list of types where target-typed new is allowed. To
simplify, we propose that we specify that target-typed new should produce a fully-typed new
and
the legality of that expression is defined elsewhere. This does make new()
work on enums, which
is currently proposed as illegal because it may be confusing. However, new Enum()
is legal
today, so we think that it should be allowed for target-typed new
simply because of
consistency.
There's some debate on what it should do for nullable value types. On the one hand, the rule
"new() is just shorthand for writing out the type on the left," implies that the result should be
null
. On the other hand, the nullable lifting rules would imply that the base type of the
target should be the underlying type, not the nullable type. Overall, we think that new
ing the
underlying type makes the most sense, both because it's the most useful (we already have a
shorthand for null
) and because it's likely what the user intended.
For dynamic
, we will not permit it simply because new dynamic()
is also illegal.
Final thought: many thanks to @alrz for the great contribution!