Skip to content

Elide redundant null checks #1826

@jnyrup

Description

@jnyrup

Is your feature request related to a problem? Please describe.
I noticed various null checks in the generated code that didn't need to be there or could be reduced from two checks to a single check.
I'm aware of #1614 but since that seems to (only?) be about correctness I created a separate issue for these examples.

Describe the solution you'd like
In each example below the generated code performs more null checks that necessary.
Ideally we emit C# code that performs the minimum number of null checks.

Example 1

source is null checked twice, explicitly with == null and implicitly through .Value.

[Mapper]
public partial class A
{
    public partial int? Map(int? source);
}

Actual

// <auto-generated />
#nullable enable
public partial class A
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial int? Map(int? source)
    {
        return source == null ? default(int?) : source.Value;
    }
}

Expected

// <auto-generated />
#nullable enable
public partial class A
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial int? Map(int? source)
    {
        return source;
    }
}

Example 2

Like in example 1 source is null checked twice, but to extract the value from decimal? we can use Nullable<T>.GetValueOrDefault() to save the null check inside Nullable<T>.Value.

[Mapper]
public partial class E
{
    public partial int? Map(decimal? source);
}

Actual

// <auto-generated />
#nullable enable
public partial class E
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial int? Map(decimal? source)
    {
        return source == null ? default(int?) : (int)source.Value;
    }
}

Expected

// <auto-generated />
#nullable enable
public partial class E
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial int? Map(decimal? source)
    {
        return source == null ? default(int?) : (int)source.GetValueOrDefault();
    }
}

Example 3

Each item of source is null checked.

#nullable enable
[Mapper(UseDeepCloning = true)]
public partial class B
{
    public partial List<string?> Map(List<string?> source);
}
#nullable restore

Actual

// <auto-generated />
#nullable enable
public partial class B
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial global::System.Collections.Generic.List<string?> Map(global::System.Collections.Generic.List<string?> source)
    {
        var target = new global::System.Collections.Generic.List<string?>(source.Count);
        foreach (var item in source)
        {
            target.Add(item == null ? default : item);
        }
        return target;
    }
}

Expected

// <auto-generated />
#nullable enable
public partial class B
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial global::System.Collections.Generic.List<string?> Map(global::System.Collections.Generic.List<string?> source)
    {
        var target = new global::System.Collections.Generic.List<string?>(source.Count);
        foreach (var item in source)
        {
            target.Add(item);
        }
        return target;
    }
}

Example 4

Each item of source is null checked.
I don't know if it's related but furthermore I would expect that we just called Enumerable.ToList<int?>(IEnumerable<int?> source)
like a generated mapper from List<int> to List<int> does.

[Mapper(UseDeepCloning = true)]
public partial class C
{
    public partial List<int?> Map(List<int?> source);
}

Actual

// <auto-generated />
#nullable enable
public partial class C
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial global::System.Collections.Generic.List<int?> Map(global::System.Collections.Generic.List<int?> source)
    {
        return global::System.Linq.Enumerable.ToList(source);
    }
}

Expected

// <auto-generated />
#nullable enable
public partial class C
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    public partial global::System.Collections.Generic.List<int?> Map(global::System.Collections.Generic.List<int?> source)
    {
        var target = new global::System.Collections.Generic.List<int?>(source.Count);
        foreach (var item in source)
        {
            target.Add(item);
        }
        return target;
    }
}

Example 5

Each item.Key of source in MapToDictionaryOfStringAndInt32 is null checked.

#nullable disable
[Mapper(UseDeepCloning = true)]
public partial class D
{
    public partial MyClass Map(MyClass source);
}

public class MyClass
{
    public Dictionary<string, int> MyProperty { get; set; }
}
#nullable enable

Actual

// <auto-generated />
#nullable enable
public partial class D
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(source))]
    public partial global::MyClass? Map(global::MyClass? source)
    {
        if (source == null)
            return default;
        var target = new global::MyClass();
        if (source.MyProperty != null)
        {
            target.MyProperty = MapToDictionaryOfStringAndInt32(source.MyProperty);
        }
        else
        {
            target.MyProperty = null;
        }
        return target;
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    private global::System.Collections.Generic.Dictionary<string?, int> MapToDictionaryOfStringAndInt32(global::System.Collections.Generic.IReadOnlyDictionary<string?, int> source)
    {
        var target = new global::System.Collections.Generic.Dictionary<string?, int>(source.Count);
        foreach (var item in source)
        {
            target[item.Key == null ? default : item.Key] = item.Value;
        }
        return target;
    }
}

Expected

// <auto-generated />
#nullable enable
public partial class D
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(source))]
    public partial global::MyClass? Map(global::MyClass? source)
    {
        if (source == null)
            return default;
        var target = new global::MyClass();
        if (source.MyProperty != null)
        {
            target.MyProperty = MapToDictionaryOfStringAndInt32(source.MyProperty);
        }
        else
        {
            target.MyProperty = null;
        }
        return target;
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
    private global::System.Collections.Generic.Dictionary<string?, int> MapToDictionaryOfStringAndInt32(global::System.Collections.Generic.IReadOnlyDictionary<string?, int> source)
    {
        var target = new global::System.Collections.Generic.Dictionary<string?, int>(source.Count);
        foreach (var item in source)
        {
            target[item.Key] = item.Value;
        }
        return target;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions