This is similar to this question Call overloaded generic method from generic method but it's not the same: in that case, the method returned void
, while I need to return the generic type T
, so the proposed solution does not work.
I'm implementing a tuple-like class which has generic parameters and accessors for these parameters:
public class MyClass<T1, T2>
{
private readonly T1 _item1;
private readonly T2 _item2;
public MyClass(T1 item1, T2 item2)
{
_item1 = item1;
_item2 = item2;
}
public T1 GetItem(T1 _) => _item1;
public T2 GetItem(T2 _) => _item2;
private T GetItem<T>(T x)
{
throw new ArgumentException();
// return x;
}
public T Get<T>()
{
return GetItem(default(T));
}
}
I would like to use this class like this:
var obj = new MyClass<int, string>(1, "hello");
var a = obj.Get<int>();
var b = obj.Get<string>();
But if I try this, the Generic T GetItem<T>(T x)
is always called (the exception is thrown), and not the non-generic implementations.
If I try to access the GetItem
method directly, it works as intended:
var obj = new MyClass<int, string>(1, "hello");
var q = obj.GetItem(default(int));
var w = obj.GetItem(default(string));
q
and w
contain 1
and hello
as expected.
Is there a way for me to use this class as I want (using obj.Get<T>
)? I want to avoid Item1
Item2
Item3
getters.
CodePudding user response:
But if I try this, the Generic
T GetItem<T>(T x)
is always called (the exception is thrown), and not the non-generic implementations.
That's because overload resolution happens at compile-time, and at compile-time, the concrete type of T
is unknown.
Is there a way for me to use this class as I want (using
obj.Get<T>
)?
Using plain old dynamic type checking, sure:
public class MyClass<T1, T2>
{
private readonly T1 _item1;
private readonly T2 _item2;
public MyClass(T1 item1, T2 item2)
{
_item1 = item1;
_item2 = item2;
}
public T Get<T>()
{
if (typeof(T) == typeof(T1))
{
return (T)(object)_item1;
}
if (typeof(T) == typeof(T2))
{
return (T)(object)_item2;
}
throw new InvalidOperationException();
}
}
The advantage of this solution is that the behavior of the edge cases (T1 == T2, or T matches neither T1 nor T2) is properly defined and easy to see.
Alternatively, you can use the dynamic
"hack" used in the question you linked to, you just need to cast the result to T
: return (T)GetItem((dynamic)default(T));
, here's a working fiddle.
But this is just plain ugly, please don't do that. Compare the fiddle with the version above and try to judge honestly which one is easier to read, easier to understand, and, thus, easier to maintain.
CodePudding user response:
Tests
var instance = new MyClass<string, int>("hello", 1);
Console.WriteLine(instance.GetItem("hi"));
Console.WriteLine(instance.GetItem(2));
// returns "hello"
Console.WriteLine(instance.Get<string>());
// returns "1"
Console.WriteLine(instance.Get<int>());
// throws with details
Console.WriteLine(instance.Get<object>());
Implementation
public T Get<T>()
{
var parentType = typeof(MyClass<T1, T2>);
var method = parentType.GetMethod(nameof(GetItem), new[] { typeof(T) });
if (method == null)
throw new NotImplementedException(
$"No implementation for {nameof(GetItem)} with parameter type {typeof(T).FullName}");
var result = (T)method.Invoke(this, new object[] { default(T) });
return result;
}
This version uses reflection. If you need more performance you should go for compiled expressions, but that is harder to maintain
CodePudding user response:
What if you had the same data-type for the two items?
var obj = new MyClass<int, int>(1, 2);
what should GetItem
return here? Obviously there's no valid answer to this.
Anyway providing a parameter only to distinguish which method to call is pretty odd. Actually your GetItem<T>
-method shouldn't be generic at all. Just use two different methods:
T1 GetItem1() => _item1;
T2 GetItem2() => _item2;
or use auto-properties to get rid of the backing-fields:
public class MyClass<T1, T2>
{
public T1 Item1 { get; }
public T2 Item2 { get; }
public MyClass(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}