EDIT 2: in the case of your overhauled question, the approach is basically the same I've proposed you earlier.
Here I'm adapting it to your scenario and commenting better on what makes it work (plus an unfortunate "gotcha" with value types...)
// note how IPair<T> is covariant with T (the "out" keyword)
public interface IPair<out T> {
T First {get;}
T Second {get;}
}
// I get no bonus points... I've had to touch Pair to add the interface
// note that you can't make classes covariant or contravariant, so I
// could not just declare Pair<out T> but had to do it through the interface
public class Pair<T> : IPair<T> {
public T First {get; set;}
public T Second {get; set;}
// overriding ToString is not strictly needed...
// it's just to "prettify" the output of Console.WriteLine
public override string ToString() {
return String.Format("({0},{1})", First, Second);
}
}
public static class Application {
// Swap now works with IPairs, but is fully generic, type safe
// and contains no casts
public static IPair<T> Swap<T>(IPair<T> pair) {
return new Pair<T>{First=pair.Second, Second=pair.First};
}
// as IPair is immutable, it can only swapped in place by
// creating a new one and assigning it to a ref
public static void SwapInPlace<T>(ref IPair<T> pair) {
pair = new Pair<T>{First=pair.Second, Second=pair.First};
}
// now SwapAll works, but only with Array, not with List
// (my understanding is that while the Array's indexer returns
// a reference to the actual element, List's indexer only returns
// a copy of its value, so it can't be switched in place
public static void SwapAll(IPair<object>[] pairs) {
for(int i=0; i < pairs.Length; i++) {
SwapInPlace(ref pairs[i]);
}
}
}
That's more or less it... Now in your main
you can do:
var pairs = new IPair<object>[] {
new Pair<string>{First="a", Second="b"},
new Pair<Uri> {
First=new Uri("http://www.site1.com"),
Second=new Uri("http://www.site2.com")},
new Pair<object>{First=1, Second=2}
};
Application.SwapAll(pairs);
foreach(var p in pairs) Console.WriteLine(p.ToString());
OUTPUT:
(b,a)
(http://www.site2.com/,http://www.site1.com/)
(2,1)
So, your Array is type-safe, because it can only contain Pair
s (well, IPair
s). The only gotcha is with value types. As you can see I had to declare the last element of the array as a Pair<object>
instead of Pair<int>
as I would have liked.
This is because covariance/contravariance don't work with value types so I had to box int
in an object
.
=========
EDIT 1 (old, just leaving there as reference to make sense of the comments below):
you could have both a non-generic marker interface for when you need to act on the container (but don't care about the "wrapped" type) and a covariant generic one for when you need the type information.
Something like:
interface IFoo {}
interface IFoo<out T> : IFoo {
T Value {get;}
}
class Foo<T> : IFoo<T> {
readonly T _value;
public Foo(T value) {this._value=value;}
public T Value {get {return _value;}}
}
Suppose you have this simple hierarchy of classes:
public class Person
{
public virtual string Name {get {return "anonymous";}}
}
public class Paolo : Person
{
public override string Name {get {return "Paolo";}}
}
you could have functions that work either on any IFoo
(when you don't care if Foo
wraps a Person
) or specifically on IFoo<Person>
(when you do care):
e.g.
static class Functions
{
// this is where you would do DoSomethingWithContainer(IFoo<?> foo)
// with hypothetical java-like wildcards
public static void DoSomethingWithContainer(IFoo foo)
{
Console.WriteLine(foo.GetType().ToString());
}
public static void DoSomethingWithGenericContainer<T>(IFoo<T> el)
{
Console.WriteLine(el.Value.GetType().ToString());
}
public static void DoSomethingWithContent(IFoo<Person> el)
{
Console.WriteLine(el.Value.Name);
}
}
which you could use like this:
// note that IFoo can be covariant, but Foo can't,
// so we need a List<IFoo
var lst = new List<IFoo<Person>>
{
new Foo<Person>(new Person()),
new Foo<Paolo>(new Paolo())
};
foreach(var p in lst) Functions.DoSomethingWithContainer(p);
foreach(var p in lst) Functions.DoSomethingWithGenericContainer<Person>(p);
foreach(var p in lst) Functions.DoSomethingWithContent(p);
// OUTPUT (LinqPad)
// UserQuery+Foo`1[UserQuery+Person]
// UserQuery+Foo`1[UserQuery+Paolo]
// UserQuery+Person
// UserQuery+Paolo
// anonymous
// Paolo
One notable thing in the output is that even the function that only received IFoo still had and printed the full type information which in java would have been lost with type erasure.