Skip to content

Instantly share code, notes, and snippets.

@controlflow
Last active January 10, 2018 08:17
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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();
}
@ashmind
Copy link

ashmind commented Apr 5, 2014

Would be nice if it was a Google doc, so we could comment on each line. Actually standard GH repository would work as well.

@ashmind
Copy link

ashmind commented Apr 5, 2014

// chained null checks + control flow jump to the same place

Only if there is nothing after that (obvious, I know).

// conditional operator + nullable value type

Your refactoring changes type of b?

U With<T,U>(Func<T, U>) where T: class, where U: class;

Would it consider this by itself? Because by itself it is just a Kestrel combinator, I use it all the time in test code.

@controlflow
Copy link
Author

Your refactoring changes type of b?

Nope, operator ?? bring back int type.

Would it consider this by itself?

Maybe. Maybe just with NotNull name. Kestrel combinator? Seriously? Can you provide an example?

@ashmind
Copy link

ashmind commented Apr 8, 2014

Nope, operator ?? bring back int type.

Yes, so it changes the type of b if B is int?. Or is it only for non-nullable B?

Kestrel combinator? Seriously? Can you provide an example?

var client = New.Client().With(c => c.Name = "ABC");

var something = new Something {
    A = "A",
    B = "B"
}.With(s => s.Event += E)

var something = new Something {
    A = "A"
    B = new SomethingLikeXmlDocument().With(d => d.LoadUsingMagic("xyz"))
};

Often useful as an object initializer replacement for features that are not supported by object initializers and APIs that do not support chaining.

@controlflow
Copy link
Author

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 :)

@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