Why can't I overload a generic method based on class/struct?

J

jehugaleahsa

Hello:

I was hoping to write a method that returned a T? for value types and
a T for reference types. I assumed that I could overload a generic
method based on the constraint of class or struct.

The intention was to return null for any type, using Nullable<T> for
value types and plain null for references.

Why can't the compiler distinquish?

Thanks,
Travis
 
M

Marc Gravell

In short, you simply can't... the language spec doesn't permit overloads
separated purely by constraints.

But you can just use null! The following does exactly what you want...

int? a = null;
string b = null;
if (a != null) {...}
if (b != null) {...}

In the case of generics, default(T) would also return either a null (class),
an empty-nullable (nullable-T) or a zero-value (struct).

Note that there appear to be some minor JIT optimisation issues with
unconstrained generic use of null tests, but even so, within a generic with
"T foo", testing foo against null will do the right thing for all 3 cases.

For some specific cases (benchmarked etc), I do have a suitable IsNull
method tucked away... but I honestly don't think you need it! If you let me
know more about what you are trying to do (and whay is/isn't working) I can
show you how it works... but I don't want to post it without reason, as it
might add more confusion than necessary...

Marc
 
J

jehugaleahsa

In short, you simply can't... the language spec doesn't permit overloads
separated purely by constraints.

But you can just use null! The following does exactly what you want...

            int? a = null;
            string b = null;
            if (a != null) {...}
            if (b != null) {...}

In the case of generics, default(T) would also return either a null (class),
an empty-nullable (nullable-T) or a zero-value (struct).

Note that there appear to be some minor JIT optimisation issues with
unconstrained generic use of null tests, but even so, within a generic with
"T foo", testing foo against null will do the right thing for all 3 cases.

For some specific cases (benchmarked etc), I do have a suitable IsNull
method tucked away... but I honestly don't think you need it! If you let me
know more about what you are trying to do (and whay is/isn't working) I can
show you how it works... but I don't want to post it without reason, as it
might add more confusion than necessary...

Marc

Well, in my scenario, I am implementing a method similar to Left Outer
Join. I want all the elements from the outer list, even if there is
not a matching inner list element. I can easily make one of the types
Nullable, but then the rest of the arguments become more difficult.
For instance, I can't pass in Comparer<int>.Default.Compare anymore
because one of the arguments is Nullable. So I have to write method
like this:

delegate(int lhs, int? rhs)
{
return lhs < rhs ? -1 : rhs < lhs ? 1 : 0;
}

and instead of passing in two int[] I have to pass in an int?[] which
is just cumbersome.

That is why I was hoping for the overloading. There is no point in
making a generic method if it is so complicated to get the syntax
right that it ends up being longer than writing the method over again.
Here is my example method:

/// <summary>
/// Performs a left outer join on ranges sorted by the key
value.
/// </summary>
/// <remarks>The collections must be sorted by key.</remarks>
public static IEnumerable<KeyValuePair<TFirst, TSecond>>
LeftOuterJoin<TFirst, TSecond>(IList<TFirst> lhs,
IList<TSecond> rhs,
Comparison<TFirst, TSecond> keyComparison)
{
Debug.ArgumentNull(lhs, "lhs");
Debug.ArgumentNull(rhs, "rhs");
Debug.ArgumentNull(keyComparison, "keyComparison");
int lhsFirst = 0;
int rhsFirst = 0;
int position = rhsFirst;
bool found = false;
while (lhsFirst != lhs.Count)
{
int result = keyComparison(lhs[lhsFirst],
rhs[position]);
if (result < 0)
{
if (!found)
{
yield return new KeyValuePair<TFirst,
TSecond>(lhs[lhsFirst], default(TSecond));
}
else
{
found = false;
}
++lhsFirst;
position = rhsFirst;
}
else if (result > 0)
{
++position;
rhsFirst = position;
}
else
{
yield return new KeyValuePair<TFirst,
TSecond>(lhs[lhsFirst], rhs[position]);
++position;
found = true;
}
if (position == rhsCount)
{
position = rhsFirst;
if (!found)
{
yield return new KeyValuePair<TFirst,
TSecond>(lhs[lhsFirst], default(TSecond));
}
else
{
found = false;
}
++lhsFirst;
}
}
}

To call this with char, char? looks like this:

char[] lhs = new char[] { 'A', 'B', 'E', 'F' };
char?[] rhs = new char?[] { 'A', 'A', 'B', 'C', 'D',
'F' };
IEnumerable<KeyValuePair<char, char?>> pairs
= Algorithms.LeftOuterJoin<char, char?>(lhs, rhs,
delegate(char l, char? r)
{
return l < r ? -1 : r < l ? 1 : 0;
});
List<KeyValuePair<char, char?>> result = new
List<KeyValuePair<char, char?>>(pairs);

It's very uncomfortable.
 
R

Rene

Assuming I understand your question correctly..

Regardless whether the return type is generic or not, the C# compiler will
not allow you to overload a method based on the return type of the method.
So for example, the following won't compile:

public Exception DoIt()
public EventArgs DoIt()

But you already knew that :)
Why can't the compiler distinquish?

I believe this is a limitation on the C# compiler, if you where to write
your own IL then I think the runtime is capable of overloading method using
the return value as the method signature.
 
J

jehugaleahsa

Assuming I understand your question correctly..

Regardless whether the return type is generic or not, the C# compiler will
not allow you to overload a method based on the return type of the method.
So for example, the following won't compile:

public Exception DoIt()
public EventArgs DoIt()

But you already knew that :)


I believe this is a limitation on the C# compiler, if you where to write
your own IL then I think the runtime is capable of overloading method using
the return value as the method signature.










- Show quoted text -

What about having different method names . . . is that a good idea? I
don't think it is. People using my code don't really want to have to
distinguish between one or the other. Sigh . . . I guess I will stick
to the ugly formatting.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top