Last active
January 10, 2018 08:17
-
-
Save controlflow/9996185 to your computer and use it in GitHub Desktop.
C# 6.0 null-safe member access/method/indexer call use cases
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
nullness inspection | |
{ | |
a?.M(); | |
a.F(); // <= possible NRE | |
a?.M(a != null); // <= expression always true | |
} | |
inspection "Redundant null-propagation" | |
{ | |
var a = new A(); | |
a?.M( // <= redundant ? warning | |
a?.P); // <= redundant ? warning | |
} | |
inspection "Merge sequential null-checks" | |
{ | |
if (a != null && a.P != null && a.P.U != null) | |
if (a != null && a.P.U != null) | |
if (a != null && a.P.M(x) != null) // invocation at end | |
if (_ && a?.P != null && a.P?.U != null) | |
if (a?.P != null && a.P.U != null) | |
if (a == null || a.P == null) | |
if (s.HasValue && s.Value.P != null) | |
if (d != null && d(x) != null) | |
// => | |
if (a?.P?.U != null) | |
if (a?.P.U != null) // short-circuiting | |
if (a?.P.M(x) != null) | |
if (_ && a?.P?.U != null) | |
if (a?.P?.U != null) | |
if (a?.P == null) | |
if (s?.P != null) // value-type | |
if (d?.Invoke(x) != null) // delegate | |
} | |
inspection "Null-propagating method call(s)" | |
{ | |
var t = x.Y; | |
if (t != null) { t.M(); } | |
if (t == null) { } else { t.P.M(); } | |
if (t != null) { | |
var p = t.P as X; | |
p.M(); | |
p?.U(); | |
if (p != null) { G(); } | |
} | |
if (t is X) { ((X) t).M(); } | |
// => | |
var t = x.Y; | |
t?.M(); | |
t?.P.M(); // short-circuiting | |
var p = t?.P as X; // lifted variable + as-cast | |
p?.M(); // lifted method call | |
p?.U(); | |
if (p != null) { G(); } // lifted if | |
(t as X)?.M(); | |
} | |
inspection "Null-propagating method return" | |
{ | |
if (t == null) { return null; } else { return t.M(x).P; } | |
// => | |
return t?.M(x).P; | |
} | |
inspection "Replace control instructions with null-propagation" | |
{ | |
for (;;) { | |
if (a == null) continue; | |
var b = a.B; | |
if (b == null) continue; | |
var d = b.C.D; | |
d.M(); | |
// continue | |
} | |
// => | |
for (;;) { | |
var b = a?.B | |
var d = b?.C.D; // short-circuiting | |
d?.M(); | |
} | |
} | |
inspection "Reduce nested null-checks with null-propagation" | |
{ | |
var a = GetA(); | |
if (a != null) { | |
var b = a.B; | |
if (b != null) { | |
var d = b?.C.D; | |
var e = d.E; | |
if (e != null) e.M(); | |
} | |
} | |
// => | |
var a = GetA(); | |
var b = a?.B; | |
var d = b?.C.D; // short-circuiting | |
var e = d?.E; // lifted variable | |
e?.M(); | |
} | |
inspection "Replace conditional operator with null-propagation" | |
{ | |
var b = (a == null) ? null : a.B; | |
var c = (a != null) ? a.B.C : null; | |
var d = (a != null ? a.B.C : null).D(); | |
var e = s.HasValue ? s.Value.P : null; | |
// => | |
var b = a?.B; | |
var c = a?.B.C; // short-circuiting | |
var d = (a?.B.C).D(); // parentheses required | |
var e = s?.P; // value type | |
} | |
inspection "Replace if statement with null-propagation" | |
{ | |
if (p != null) { t = p.U.T; } else { t = null; } | |
T t = null; if (p != null) { t = p.T; } | |
// => | |
t = p?.U.T; | |
T t = null; t = p?.T; | |
} | |
inspection "Replace conditional operator with null-propagation with ?? operator" | |
{ | |
// NOTE: .C is of ref/non-nullable type | |
var c = (a == null) ? 42 : a.B.C; | |
T c; if (a == null) { c = 42; } else { c = a.B.C; } | |
// => | |
var c = a?.B.C ?? 42; | |
} | |
inspection "Replace null-checked access with null-propagation" | |
{ | |
if (t != null) t = t.T; | |
// => | |
t = t?.T; | |
} | |
inspection "Merge sequential null-checks (using lifted nullable operators)" | |
{ | |
if (t != null && t.M()) | |
if (t != null && t.P == 42) | |
// => | |
if (t?.M() == true) | |
if (t?.P == 42) | |
} | |
// inside LINQ expressions | |
{ | |
xs.Where(x => x != null).Select(x => x.B.C).Where(x => x != null) | |
xs.Where(x => x.A != null).Select(x => x.A.B.C).Where(x => x != null) | |
from x in xs where x.A != null | |
where x.A.B != null select x.A.B | |
// => | |
xs.Select(x => x?.B.C).Where(x => x != null) | |
xs.Select(x => x.A?.B.C).Where(x => x != null) | |
from x in xs where x.A?.B != null select x.A.B | |
} | |
// user's .NotNull(x => x.Y)-like methods: | |
{ | |
T t = a.With(_ => _.B); | |
// U With<T,U>(Func<T, U>) where T: class, where U: class; | |
// note: false positives. Think about names like 'NotNull'/'NotNull'/'IfNotNull'/'Try' | |
// U? With<T,U>(Func<T?,U>) where T: struct, where U: struct; | |
// U? With<T,U>(Func<T?,U?>) where T: struct, where U: struct; | |
// U With<T,U>(Func<T,U>) where T: class; | |
// + note: 'struct => class' and 'class => struct' overloads | |
// + node: overloads with default values | |
// + note: parameter '_' may not be used at all | |
// => | |
T t = a?.B; | |
} | |
// the same, more complex example | |
{ | |
D d = a.NotNull(x => x.B.C).NotNull(x => x.D); | |
// => | |
D d = a?.B.C?.D; // short-circuiting | |
} | |
// delegate invoke | |
{ | |
D d = o as D; | |
d(); // possible NRE | |
// => | |
D d = o as D; | |
d?.Invoke(); | |
} |
Or is it only for non-nullable
B
?
Nice catch, only if it is non-nullable. Thanks!
Often useful as an object initializer replacement...
Thanks, nice point. I think we will stick with hardcoded NotNull
name or [ContractAnnotation(null => null)]
or smth like that. Or disabled-by-default inspection nobody will ever find :)
Maybe: detect expression that will behave the same after transformation to nullable type
if (x is T && ((T) x).Value == 42)
// =>
if ((x as T)?.Value == 42) // lifted ==
Offer transformation via context action, not QF.
Maybe: ability to lift whole declarations (only as context action, not suggestion)
if (person != null) {
var something = person.GetSomething(arg, 42);
var component = something["argument"].Component;
component.Foo(something);
}
=>
var something = person?.GetSomething(arg, 42);
var component = something?["argument"].Component;
component?.Foo(something /* only when T is not value type */);
TODO: reverse transformation!
var function = functionDeclaration.DeclaredElement;
if (function == null) return null;
return function.ReturnType;
=>
var function = functionDeclaration.DeclaredElement;
return function?.ReturnType;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yes, so it changes the type of
b
ifB
isint?
. Or is it only for non-nullableB
?Often useful as an object initializer replacement for features that are not supported by object initializers and APIs that do not support chaining.