ScubaSteve
ScubaSteve

Reputation: 8270

C# Method resolution does not work as expected at runtime

I looked at https://stackoverflow.com/a/5174773/787958 and the method resolution is not working as expected in my instance.

I have JSON coming into a method that I want to create a typed object from and then validate it.

var type = Type.GetType($"{Namespace.Models}.{typeName.FirstCharToUpper()}");
var model = JsonConvert.DeserializeObject(json, type);
var validator = new Validator();
var isValid = await validator.ValidateAsync(model);

The Validator class looks as such:

public class Validator : IValidator
{
    public Task<Boolean> ValidateAsync(Object model) { return Task.FromResult(true); }

    public Task<Boolean> ValidateAsync(User user)
    {
        if (user.Id == null || user.Id == Guid.Empty)
            return Task.FromResult(false);

        if (String.IsNullOrWhiteSpace(user.Name))
            return Task.FromResult(false);

        return Task.FromResult(true);
    }
}

Even though at runtime, type is User, it does not call the ValidateAsync(User user) method, instead it calls ValidateAsync(Object model) method.

But, if the type is not dynamic at compile time and instead I do:

var model = JsonConvert.DeserializeObject<User>(json);

It properly calls the ValidateAsync(User user) method.

Is there a proper way to have a dynamic type at compile time, but known at runtime, and have it call the expected "more specific" method?

UPDATE

Having any "future coder" add to the switch statement was not too much of an ask imo, since they would be in that class adding their new ValidateAsync(NewModel model) method anyways. Utilizing dynamic was more along the lines of how I would like the code to be as it would not bloat the code with a possibly larger and larger switch statement. But, I was worried about performance impact. So I ran a Postman runner 250 times with the switch statement (just User or default) and 250 times with dynamic. The differences in performance was negligible.

Average response time with switch: 2.668ms

Average response time with dynamic: 2.816ms

So, I'm going to go with the dynamic solution. Thanks!

Upvotes: 1

Views: 103

Answers (2)

Guru Stron
Guru Stron

Reputation: 141835

Overload resolution happens at the compile time, not in the runtime. model created by var model = JsonConvert.DeserializeObject(json, type); will be of object type so compiler will expectedly select ValidateAsync(Object model) overload.

You may try using dynamic for late bound (runtime) resolution:

dynamic model = JsonConvert.DeserializeObject(json, type);
var validator = new Validator();
var isValid = await validator.ValidateAsync(model);

But note that it comes with some performance cost.

Another option is type testing and casting. For example with pattern matching can look like this:

var isValid = model switch
{
    User user => await validator.ValidateAsync(user),
    _ =>  await validator.ValidateAsync(model)
}

Upvotes: 1

John Wu
John Wu

Reputation: 52240

The type of method resolution you're thinking of happens at compile time.

If you want to resolve the method at run time you have to write the code for it yourself, e.g.

public Task<Boolean> ValidateAsync(Object model)
{
    return (model is User) ? ValidateAsync((User)model) : Task.FromResult(true);
}

Upvotes: 1

Related Questions