Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Save space by removing delegates from TryPick and Map autogenerated implementations #72

Closed
zspitz opened this issue Dec 28, 2020 · 3 comments · Fixed by #80
Closed

Comments

@zspitz
Copy link
Contributor

zspitz commented Dec 28, 2020

The autogenerated code for the TryPickX method currently looks like this:

public OneOf<T0, T1, TResult, T3> MapT2<TResult>(Func<T2, TResult> mapFunc)
{
    if(mapFunc == null)
    {
        throw new ArgumentNullException(nameof(mapFunc));
    }
    return Match<OneOf<T0, T1, TResult, T3>>(
        input0 => input0,
        input1 => input1,
        input2 => mapFunc(input2),
        input3 => input3
    );
}

Each delegate requires the compiler to generate its own class. Similarly, for Map:

public OneOf<T0, T1, TResult, T3> MapT2<TResult>(Func<T2, TResult> mapFunc)
{
    if(mapFunc == null)
    {
        throw new ArgumentNullException(nameof(mapFunc));
    }
    return Match<OneOf<T0, T1, TResult, T3>>(
        input0 => input0,
        input1 => input1,
        input2 => mapFunc(input2),
        input3 => input3
    );
}

Using autogenerated implementations that don't have delegates, e.g. for TryPick:

public bool TryPickT1(out T1 value, out OneOf<T0, T2, T3, T4> remainder)
{
    value = IsT1 ? AsT1 : default;
    switch (_index)
    {
        case 0: remainder = OneOf<T0, T2, T3, T4>.FromT0(AsT0); break;
        case 1: remainder = default; break;
        case 2: remainder = OneOf<T0, T2, T3, T4>.FromT1(AsT2); break;
        case 3: remainder = OneOf<T0, T2, T3, T4>.FromT2(AsT3); break;
        case 4: remainder = OneOf<T0, T2, T3, T4>.FromT3(AsT4); break;
        default: throw new InvalidOperationException();
    }
    return IsT1;
}

and for Map

public OneOf<T0, TResult, T2, T3, T4> MapT1<TResult>(Func<T1, TResult> mapFunc)
{
    if(mapFunc == null)
    {
        throw new ArgumentNullException(nameof(mapFunc));
    }
    switch (_index)
    {
        case 0: return OneOf<T0, TResult, T2, T3, T4>.FromT0(AsT0);
        case 1: return OneOf<T0, TResult, T2, T3, T4>.FromT1(mapFunc(AsT1));
        case 2: return OneOf<T0, TResult, T2, T3, T4>.FromT2(AsT2);
        case 3: return OneOf<T0, TResult, T2, T3, T4>.FromT3(AsT3);
        case 4: return OneOf<T0, TResult, T2, T3, T4>.FromT4(AsT4);
        default: throw new InvalidOperationException();
    }
}

reduces the file size, as follows:

File Current Reduced
OneOf.dll 233K 106K
OneOf.Extended.dll 9.9M 1.9M

I've never had much need to consider this before, but I'm wondering if there are other ways to reduce the size of the IL? Maybe use later language features such as switch expressions? Or perhaps replace internal calls to FromX with a generic FromIndex method with a generic parameter?

How much of a reduction would be needed to move OneOf.Extended back into the original dll?

@mcintyre321
Copy link
Owner

mcintyre321 commented Dec 28, 2020 via email

@zspitz
Copy link
Contributor Author

zspitz commented Dec 28, 2020

Would you consider using a higher C# language version? Most of the new syntax is supported even on earlier framework versions (https://medium.com/@SergioPedri/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb). Switch expressions in particular could be very useful.

If nothing else, it would make the auto-generated code easier to maintain, by outputting less.

@zspitz
Copy link
Contributor Author

zspitz commented Dec 28, 2020

Using static doesn't seem to make much of a difference. Looking at the output dll with ILSpy, the compiler-generated classes are all still there.

Another observation: Release is slightly smaller than Debug:

File Debug Release
OneOf.dll 240K 230K
OneOf.Extended.dll 9.8M 9.7M

@mcintyre321 Can I set the language version to C# 9? Even though there are some parts of C# 9 that aren't supported on older framework versions? As I noted above, it'll make the autogenerated code easier to maintain.

@zspitz zspitz mentioned this issue Feb 4, 2021
zspitz added a commit to zspitz/OneOf that referenced this issue Feb 15, 2021
@zspitz zspitz mentioned this issue Feb 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants