Reputation: 10015
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
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
.
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
Reputation: 32760
No you can't. Type inference does not take into account the return type of a method. TResult
might 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
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