Skip to content

Instantly share code, notes, and snippets.

@controlflow
Last active January 10, 2018 08:17
Show Gist options
  • Save controlflow/9996185 to your computer and use it in GitHub Desktop.
Save controlflow/9996185 to your computer and use it in GitHub Desktop.
C# 6.0 null-safe member access/method/indexer call use cases
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();
}
@controlflow
Copy link
Author

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.

@controlflow
Copy link
Author

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 */);

@controlflow
Copy link
Author

TODO: reverse transformation!

@controlflow
Copy link
Author

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