Skip to content

Instantly share code, notes, and snippets.

@controlflow controlflow/gist:9996185
Last active Jan 10, 2018

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Owner Author

commented Apr 6, 2014

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Owner Author

commented Apr 8, 2014

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

This comment has been minimized.

Copy link
Owner Author

commented Apr 10, 2014

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

This comment has been minimized.

Copy link
Owner Author

commented Jun 13, 2014

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

This comment has been minimized.

Copy link
Owner Author

commented Jun 14, 2014

TODO: reverse transformation!

@controlflow

This comment has been minimized.

Copy link
Owner Author

commented Jul 17, 2014

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
You can’t perform that action at this time.