Alex Zhukovskiy
Alex Zhukovskiy

Reputation: 10015

Simplify generic type inferring

I'm writing a generic code that should handle situations when data is loaded from multiple sources. I have a method with following signature:

public static TResult LoadFromAnySource<TContract, TSection, TResult>
    (this TSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TSection : ConfigurationSection
    where TResult : IDatabaseConfigurable<TContract, TSection>, new() 
    where TContract : new()

But it's an overkill: when I pass TResult, I already know what TContract and TSection exactly are. In my example:

public interface ISourceObserverConfiguration 
    : IDatabaseConfigurable<SourceObserverContract, SourceObserverSection>

But I have to write following:

sourceObserverSection.LoadFromAnySource<SourceObserverContract, 
                                        SourceObserverSection, 
                                        SourceObserverConfiguration>
    (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

You can see that I have to specify pair <SourceObserverContract, SourceObserverSection> twice, it's a violation of DRY principe. So I'd like to write something like:

sourceObserverSection.LoadFromAnySource<SourceObserverConfiguration>
   (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

and make SourceObserverContract and SourceObserverSection inferred from interface.

Is it possible in C# or I should specify it everywhere manually?

IDatabaseConfigurable looks like:

public interface IDatabaseConfigurable<in TContract, in TSection> 
    where TContract : ConfigContract
    where TSection : ConfigurationSection
{
    string RemoteName { get; }

    void LoadFromContract(TContract contract);

    void LoadFromSection(TSection section);
}

Then extension just calls these two methods based on some logic. I must specify types becuase I need to access properties of each specific realisation, so I need a covariance.

Upvotes: 10

Views: 650

Answers (3)

David Pine
David Pine

Reputation: 24525

With the current method signature of the LoadFromAnySource method, this cannot be inferred as you'd like. However, this can be inferred with a modification to the LoadFromAnySource signature.

Since you already know the ISourceObserverConfiguration interface (and from this we know that it re-implements the IDatabaseConfigurable<SourceObserverContract, SourceObserverSection> interface), use that as a generic constraint instead in your method declaration:

Instead of

public static TResult LoadFromAnySource<TContract, TSection, TResult>
    (this TSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TSection : ConfigurationSection
    where TResult : IDatabaseConfigurable<TContract, TSection>, new() 
    where TContract : new()

Use this

public static TResult LoadFromAnySource<TResult>
    (this SourceObserverSection section, 
          string serviceBaseUri, 
          string nodeName)
    where TResult : ISourceObserverConfiguration, new()

This removes the need for the TContract and TSection as they are known in the ISourceObserverConfiguration interface. The compiler knows that the interface constraint is IDatabaseConfigurable<SourceObserverContract, SourceObserverSection> and it will just work.

Additionally, since this is an extension method and we're defining a generic constraint on ISourceObserverConfiguration, we need to extend SourceObserverSection.


Then you can consume it exactly like you desire:

sourceObserverSection.LoadFromAnySource<SourceObserverConfiguration>
   (_registrationServiceConfiguration.ServiceBaseUri, nodeName);

Update

Based on the OP's modifications/clarifications to the question, I have the following:

Is it possible in C# or I should specify it everywhere manually?

You should specify it manually. It is not possible with to infer this based on the requirement of having a re-implementation where the base interface defines the concrete type that your top-level constraint is attempting to resolve. In other words, since you have multiple implementations of IDatabaseConfigurable the caller must specify which implementation to use via its TContract and TSection constraints.

Upvotes: 1

InBetween
InBetween

Reputation: 32760

No you can't. Type inference does not take into account the return type of a method. TResultmight contain all the information needed but type inference will not use it.

You'll need to make TContract part of the method's signature so that the type can be inferred. TResult is redundant, there is no need for it to be generic, simply use IDataBaseConfigurable<TContract, TSection> as the method's return type.

Upvotes: 2

Rob
Rob

Reputation: 27357

It kind of depends how flexible your code is, and what you do with it. In general, no - you either need to specify all the generic types, or none of them.

This means that simply passing TResult does not mean that the other generic types are resolved (even though logically, they can be).

Depending on how much you can change your definitions, you can make a bit tidier:

public static class Helper
{
    public static TResult LoadFromAnySource<TResult>(this ConfigurationSection section, string serviceBaseUri, string nodeName)
        where TResult : IDatabaseConfigurable<object, ConfigurationSection>, new()
    {
        return default(TResult);
    }
}

public class ConfigurationSection { }
public interface IDatabaseConfigurable<out TContract, out TSection> 
    where TContract : new()
    where TSection : ConfigurationSection
{ 
}

public class DatabaseConfigurable<TContract, TSection> : IDatabaseConfigurable<TContract, TSection>
    where TContract : new()
    where TSection : ConfigurationSection
{ 
}

public class SourceObserverContract { }
public class SourceObserverSection : ConfigurationSection { } 

Which lets you write:

var sect = new ConfigurationSection();
sect.LoadFromAnySource<DatabaseConfigurable<SourceObserverContract, SourceObserverSection>>("a", "B");

The difference being you put the constraint on the IDatabaseConfigurable, rather than on the method. You also need to make the interface covariant. If that is not possible with your design, then it is not possible, as far as I can see, to do what you're trying to accomplish (without having a non-generic IDatabaseConfigurable)

Upvotes: 1

Related Questions