Reputation: 177
Let's consider this example:
private TResult Invoke<TResult>(string identifier, Func<TResult> action)
where TResult : class, new()
{
if (!this.IsActive()) {
// logging
return new TResult();
}
if (this.dataBase.Exists(identifier)) {
return action();
} else {
// some more logging
return new TResult();
}
}
I've got a generic function which executes action
and does some additional check I always have to do - which is why I wrote that helper function.
It can be called like this
this.Invoke(
"GetTest",
() => {
List<string> items = new List<string>();
items = this.dataBase.Execute().GetResult().ToList();
return items;
});
If one of the checks fails, Invoke
will return an empty List<string>
in this case.
That works well.
Now, some calls return a value type (such as a boolean) rather than reference type.
In those cases my function doesn't work anymore because I've added the constraint class
in order to return a default value.
It would work if I removed those constraints and changed the code to return default
rather than new TResult()
, but that's not what I need. I don't want to have a null-reference for reference types.
I thought about overloading the Invoke
method, removing the constraints
and changing my code to return default
but methods can't be overloaded by just changing the constraints.
Of course, I could rename the method, but that doesn't seem to be that nice.
Is there anything else I can do here?
Upvotes: 0
Views: 92
Reputation: 141755
You can use this hack based on the fact that constraints are not part of the signature, but parameters are, and constraints in parameters are enforced during overload resolution:
class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }
private TResult Invoke<TResult>(string identifier, Func<TResult> action, RequireClass<TResult> _ = null)
where TResult : class, new()
{
return Invoke(identifier, action, () => new TResult());
}
// handle nullable value types
private TResult? Invoke<TResult>(string identifier, Func<TResult?> action)
where TResult : struct
{
return Invoke(identifier, action, () => (TResult?)new TResult());
}
private TResult Invoke<TResult>(string identifier, Func<TResult> action, RequireStruct<TResult> _ = null)
where TResult : struct
{
return Invoke(identifier, action, () => new TResult());
}
private TResult Invoke<TResult>(string identifier, Func<TResult> action, Func<TResult> def)
{
// your actual logic goes here
return def();
}
Invoke("GetTestRef", () => new List<object>()); // empty list
Invoke("GetTestVal", () => 1); // 0
Invoke("GetTestVal", () => (int?)1); // 0
Upvotes: 1