For me, the summary is you shouldn't think that you implement interfaces just for the sake of augmenting a generic type parameter with more typing.
AFAIK, you use interfaces to provide which are the contracts to work with a given API. Generics are just a language feature/tool to provide more typing where you would end up doing a lot of casts. Hereby, with generics you limit your API to expect arguments implementing one or more interfaces and also with some requirements using generic constraints.
For example, if you just want to accept implementations of given interface called IWhatever
, would you use generics?
public void DoStuff<T>(T whatever)
where T : IWhatever
{
}
// versus
public void DoStuff(IWhatever whatever)
{
}
BTW, without generics, how you would check that an implementation to IWhatever
is a class
and has a public
constructor? You would end up with reflection and you're code would smell compared to using generics:
public void DoStuff<T>()
where T : class, IWhatever, new()
{
}
In fact, a generic parameter can constraint that T
must inherit a given class and implement one or more interfaces:
public void DoStuff<T>(T arg)
where T : A, IEquatable<T>, IWhatever, IWhichever, IWherever
{
}
And whether if T
inherits a type with or without generic parameters or implements interfaces with or without generic parameters, it's not a good or bad design per se but, again, just a language tool that's suitable to specific cases.
Therefore, your statement...
Off the top of my head, my main concern is that IBar is not
necessarily a proper contract that defines what members a "bar" should
provide; it's only a hack to access generically typed members.
...describes a particular design flaw instead of an actual problem with typing generics using the wonders of interfaces.
Conclusion: if IBar
isn't a proper contract, then you should revisit your architecture and re-think your solution.
More background on the topic
Actually I thought that my original answer implied that I found the whole solution has a design flaw.
In summary, you're using interfaces to expose an association on certain classes which provide the type of the whole association using a generic type parameter. And you argue that you do this to be able to access such association in a less typed context:
However, I sometime need a "less" typesafe context, hence my question.
And then it's when covariance enters in action! See the following code sample:
public class SuperClass
{
}
public interface IWhatever<out TAssociation>
where TAssociation : SuperClass
{
TAssociation Association { get; }
}
public class SomeImplementation<TAssociation> : IWhatever<TAssociation>
where TAssociation : SuperClass
{
public TAssociation Association { get; set; }
}
Now let's define a derived class of SuperClass
:
public class DerivedClass : SuperClass
{
}
And see how this works like a charm:
SomeImplementation<DerivedClass> someImpl = new SomeImplementation<DerivedClass>();
// Covariance: you decide the degree of specialization of TAssociation
// interfaces' type parameter. In our case, we'll upcast TAssociation to
// the SuperClass type.
IWhatever<SuperClass> whatever = someImpl;
Clearly this is the way to go since C# 4.0.
I would say that the right way of expressing your requirement is you need a less specialized context instead of a less typed context. Covariance/contravariance is one of the most powerful features available in C# to cover this scenario when generics are involved in the equation.
This practice isn't a code smell per se. In my case, I go for it when I really need to access one or more associations somewhere where I just need to access certain members with a concrete purpose.
For example, if I'm building a tree-style hierarchy, I would define an interface like this:
public interface IHasParent<out TParent>
{
TParent Parent { get; }
}
Which enables me to do this:
IHasParent<object> withParent = someObject as IHasParent<object>;
if(withParent != null)
{
// Do stuff here if some given object has a parent object
}
But I don't create interfaces indiscriminately because some day I'll need less typed access to some properties. There should be a well defined purpose. Otherwise, you can end up turning a nice solution into a code smell.
You would say don't repeat yourself but I still feel that there's no definitive answer without analyzing your project code base and checking how you really use this kind of interfaces to solve concrete problems.
So, strictly talking, if you use the whole pattern when it's really required, it should be a good design decision.
Maybe you want to avoid the unavoidable
Based on some chat we've had both the OP and me, I feel that the best conclusion is that the OP wants to avoid the unaviodable.
In an object-oriented language like C# interfaces are the right tool to both define type contracts and expose a subset of a full type implementing some interface.
Also, the OP would love a feature in C# like protocols where a class that implicitly fullfils an interface is enough to consider that it implements the interface which would save up many code lines if C# could have this feature:
public interface IWhatever
{
void DoStuff();
}
public class X
{
void DoStuff();
}
public class Y
{
public void HandleStuff(IWhatever whateverImpls)
{
}
}
Y y = new Y();
// Protocols would support passing an instance of X which may not implement
// IWhatever but it implicitly fulfills IWhatever:
y.HandleStuff(new X());
BTW, C# lacks this feature. Therefore, it's a waste of time scratching your head thinking how sweet would be having such feature. You need to deal with what C# has to offer already.
Anyway, if you just need to expose some associations across your object graph and get them selectively, you can use the wonders of interfaces using a more simplified approach than yours. Did you know that you can explicitly implement the same interface more than once if its generic arguments vary?
Why don't you design an interface like this:
public interface IHasAssociation<out TAssociation>
{
TAssociation Association
{
get;
}
}
public interface IHasManyAssociation<out TEnumerable, out TAssociation>
where TEnumerable : IEnumerable<TAssociation>
where TAssociation : Entity
{
TEnumerable Association
{
get;
}
}
public class Entity
{
}
public class Company : Entity
{
}
public class CustomerProfile : Entity
{
}
public class Contact : Entity
{
}
public class Customer :
IHasAssociation<Company>,
IHasAssociation<CustomerProfile>,
IHasManyAssociation<IList<Contact>, Contact>
{
public Company Company
{
get;
set;
}
public CustomerProfile Profile
{
get;
set;
}
public IList<Contact> Contacts
{
get;
set;
}
Company IHasAssociation<Company>.Association => Company;
CustomerProfile IHasAssociation<CustomerProfile>.Association => Profile;
IList<Contact> IHasManyAssociation<IList<Contact>, Contact>.Association => Contacts;
}
Definitively this keep things simpler (KISS!) because you don't need a parallel interface object graph definition, you simply define an interface to being able to get an association of a given type:
var customer = new Customer();
customer.Profile = new CustomerProfile();
customer.Company = new Company();
customer.Contacts = new List<Contact>();
var withCompany = customer as IHasAssociation<Company>;
var withCustomerProfile = customer as IHasAssociation<CustomerProfile>;
var withContacts = customer as IHasManyAssociation<IList<Contact>, Contact>;
if (withCompany != null)
{
Company company = withCompany.Association;
Console.WriteLine("This object has an associated company!");
}
if (withCustomerProfile != null)
{
CustomerProfile profile = withCustomerProfile.Association;
Console.WriteLine("This object has a profile!");
}
if (withContacts != null)
{
IList<Contact> contacts = withContacts.Association;
Console.WriteLine("This object has contacts!");
}
Also, see covariance in action:
if(customer is IHasManyAssociation<IEnumerable<Contact>, Contact>)
{
Console.WriteLine("This object has an enumerable of contacts!");
}
Or here's how you would get all association values of an implementor of one or many IHasAssociation<out TAssociation>
interface implementations:
var entityAssociations = typeof(Customer)
.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHasAssociation<>))
.Select(i => i.GetProperty("Association").GetValue(customer));
foreach(var entityAssociation in entityAssociations)
{
Console.WriteLine($"{entityAssociation.GetType().FullName}");
}
This is the real beauty of generic programming! And remember: you won't need to implement IHasAssociation<out TAssociation>
/IHasManyAssociation<out TEnumerable, out TAssociation>
indiscriminately. That is, you implement on the classes to which associations need to be extracted in some place where you don't care who's the concrete owner of the association and you just need the association itself.