Shawn
Shawn

Reputation: 177

Constraint for valueType

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

Answers (1)

Guru Stron
Guru Stron

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

Related Questions