How can I rewrite this method using a pattern-matching switch?
internal ResultType Fetch<ResultType>(string field)
{
var type = typeof(ResultType);
object xx = type switch
{
_ when type == typeof(DateTime) => FetchDateTime(field),
_ when type == typeof(DateOnly) => FetchDateOnly(field),
_ when type == typeof(bool) => FetchBool(field),
_ when type == typeof(decimal) => FetchDecimal(field),
_ when type == typeof(string) => FetchString(field),
_ when type == typeof(int) => FetchInt(field),
_ => throw new NotImplementedException($"this[field: {field}]")
};
if (xx is ResultType rt) return rt;
return default(ResultType)!;
}
Posting as an answer because the code is too long for a comment.
You can certainly make this more efficient (ignoring the length aspect). The JIT recognises the pattern if (type == typeof(DateTime)) etc, and will emit code which keeps just the relevant parts of the method for a given ResultType.
For instance, your code results in the following when ResultType is DateTime:
C.Fetch[[System.DateTime, System.Private.CoreLib]](System.String)
L0000: push ebp
L0001: mov ebp, esp
L0003: push esi
L0004: sub esp, 8
L0007: mov esi, edx
L0009: mov ecx, 0x6e099a4
L000e: call 0x060a300c
L0013: lea edx, [eax+4]
L0016: xor ecx, ecx
L0018: mov [edx], ecx
L001a: mov [edx+4], ecx
L001d: mov edx, eax
L001f: test edx, edx
L0021: je short L002b
L0023: cmp dword ptr [edx], 0x6e099a4
L0029: je short L003a
L002b: xor eax, eax
L002d: mov [esi], eax
L002f: mov [esi+4], eax
L0032: lea esp, [ebp-4]
L0035: pop esi
L0036: pop ebp
L0037: ret 4
L003a: add eax, 4
L003d: mov edx, [eax]
L003f: mov eax, [eax+4]
L0042: mov [ebp-0xc], edx
L0045: mov [ebp-8], eax
L0048: mov eax, esi
L004a: mov edx, [ebp-0xc]
L004d: mov [eax], edx
L004f: mov edx, [ebp-8]
L0052: mov [eax+4], edx
L0055: lea esp, [ebp-4]
L0058: pop esi
L0059: pop ebp
L005a: ret 4
However the code:
internal ResultType Fetch2<ResultType>(string field)
{
var type = typeof(ResultType);
if (type == typeof(DateTime))
{
return (ResultType)(object)FetchDateTime(field);
}
if (type == typeof(DateOnly))
{
return (ResultType)(object)FetchDateOnly(field);
}
throw new NotImplementedException($"this[field: {field}]");
}
Results in:
C.Fetch2[[System.DateTime, System.Private.CoreLib]](System.String)
L0000: push ebp
L0001: mov ebp, esp
L0003: xor eax, eax
L0005: mov [edx], eax
L0007: mov [edx+4], eax
L000a: pop ebp
L000b: ret 4
This is the pattern that the runtime uses.
See on SharpLab.
As canton7's answer said, it is more efficient at runtime if you check ResultType against other types using ==, as that is what the JIT looks out for and optimises.
If you don't care about that, you can make your code "look prettier" by using a dictionary:
Dictionary<Type, Func<string, object>> dict = new()
{
{ typeof(DateTime), Wrap(FetchDateTime) },
{ typeof(string), FetchString },
// and so on
};
Here I am assuming that the FetchXXX methods have the appropriate return type based on their name. Note that since value types don't do covariance, you need an extra Wrap helper to box the return values. Use this for the FetchXXX methods that return value types.
Func<string, object> Wrap<R>(Func<string, R> f) where R: struct
=> x => f(x);
Then you can use this dictionary like this:
ResultType Fetch<ResultType>(string field)
{
if (dict.TryGetValue(typeof(ResultType), out var f))
{
var result = f(field);
return result is ResultType r ? r : default;
}
else
{
throw new NotImplementedException($"this[field: {field}]");
}
}
As a side note, if all the types that you are checking against are value types, you can actually use a pattern-matching switch, though this is very hacky. You would switch on default(ResultType).
ResultType Fetch<ResultType>(string field) where ResultType: struct
{
object xx = default(ResultType) switch
{
DateTime => FetchDateTime(field),
DateOnly => FetchDateOnly(field),
bool => FetchBool(field),
decimal => FetchDecimal(field),
int => FetchInt(field),
_ => throw new NotImplementedException($"this[field: {field}]")
};
if (xx is ResultType rt) return rt;
return default(ResultType)!;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With