7
votes

I have these classes:

/* Data classes */

public class Data
{
    public int Id { get; set; }
}

public class InfoData<TInfo> : Data
    where TInfo: InfoBase
{
    public TInfo Info { get; set; }
}

/* Info classes */

public abstract class InfoBase
{
    public int Id { get; set; }
}

public interface IRelated
{
    IList<InfoBase> Related {get; set;}
}

public class ExtraInfo : InfoBase, IRelated
{
    public string Extras { get; set; }

    public IList<InfoBase> Related { get; set; }
}

Then I have two generic methods with this signature:

public TData Add<TData>(TData data)
    where TData: Data

public TData Add<TData, TInfo>(TData data)
    where TData: InfoData<TInfo>
    where TInfo: InfoBase, IRelated

Now when I create an instance of Data class and call Add method

// data is of type Data
Add(data);

The first generic method is used and generic type Data is correctly inferred.

But when I call the same method with a more implemented type object instance

// data is of type InfoData<ExtraInfo>
// ExtraInfo is of type InfoBase and implements IRelated
Add(data);

I would expect the second generic method to be invoked, but to my surprise it's not. If I check generic type constraints on the second one being:

where TData: InfoData<TInfo>
where TInfo: InfoBase, IRelated

The first one matches and the second one as well. And these types are more implemented than simple Data type if that makes any difference.

Working example

Here is a working .Net Fiddle for you to play with.

Questions

  1. Why is second method not being called because both generic type constraints match and could be inferred?
  2. How can I rewrite my second Add method so type inference would work and won't have to explicitly provide these types just to make sure that correct overload is being used?

Edit

I've found the answer to my first question in MSDN documentation

The compiler can infer the type parameters based on the method arguments you pass in; it cannot infer the type parameters only from a constraint or return value.

In my case the first generic type can be inferred directly from parameter, but second one is more tricky. It can't be inferred from parameter only. Type constraint should be used, but compiler doesn't evaluate it.

I also have one possible solution to my second question that changes one type to concrete and keeps the other one generic.

public InfoData<TInfo> Add<TInfo>(InfoData<TInfo> data)
    where TInfo: InfoBase, IRelated

But I'm wondering if there's a more general/generic way of mitigating around this problem so I can still keep both type parameters but somehow have both types as generic?

1
Because InfoData inherits from Data. So instance of InfoData is Data => fire first method. - Zilog
@Zilog: As you're explaining it if I change method order the other one would get called? I don't think compiler evaluates method suitability as you explained it. There's likely a more complex process involved that I would like to know about so I can mitigate it. - Robert Koritnik
I don't think the sequence will mater. I just think (I am not sure) the where constrain on first method is too weak. InfoData is Data so both types triggers first method. - Zilog
I also don't understand how compiler should evaluate TInfo in your second Add method. It has only one parameter. - Zilog
Then, I'm sorry to say, what you want isn't possible. The compiler can't figure out that .Add((Data) new ExtraInfo()) (contrived, but you get the issue) is supposed to call your "more specialized" method, rather than the one matching the argument type. If you are concerned about a simple implementation, either call the second method AddInfoData so callers are forced to pick the "right" one, or use double dispatch to benefit from virtual methods (Foo.Add(Data) invokes Data.AddTo(Foo) which InfoData overrides), where the correct method will be called at runtime. - Jeroen Mostert

1 Answers

0
votes

Assuming your Add methods are in the class named DataCollector; you can add extension method which accepts InfoData<ExtraInfo> and returns the same type as shown below.

public static class DataCollectorExtensions
    {
        public static InfoData<ExtraInfo> AddInfoData(this DataCollector dataCollector, InfoData<ExtraInfo> data)
        {
            return dataCollector.Add<InfoData<ExtraInfo>, ExtraInfo>(data);
        }
    }

In this way you have to specify the generic parameters only once inside the extension method and use the extension method elsewhere without need to specify generic parameter.

new DataCollector().AddInfoData(new InfoData<ExtraInfo>());

You still have to name the method other than Add (I have named AddInfoData) otherwise compiler again picks up the public TData Add<TData>(TData data) method in the the DataCollector class. As you're infact adding InfoData this method name should be acceptable. I suppose this solution should be acceptable for all practical purposes.