c# - Overload selection and type constraint peculiarities -


i trying write universal constrainwithinbounds method let me truncate value, nullable value, or class object implements iequatable , icomparable within defined range. i'd method allow null values in parameters, treated semantically "do not bound side of range".

however, in attempts i've run strange problem don't understand.

the following 2 constrainwithinbounds method overloads came have same logic, upper 1 has generic type constraint struct , lower, class. first allows nullable value types 2nd , 3rd parameters:

public static t constrainwithinbounds<t>(this t value, t? lowerbound, t? upperbound)    t : struct, iequatable<t>, icomparable<t> {    return lowerbound.hasvalue && value.compareto(lowerbound.value) < 0       ? lowerbound.value       : upperbound.hasvalue && value.compareto(upperbound.value) > 0          ? upperbound.value          : value; }  public static t constrainwithinbounds<t>(this t value, t lowerbound, t upperbound)    t : class, iequatable<t>, icomparable<t> {    return value == null       ? null       : lowerbound != null && value.compareto(lowerbound) < 0          ? lowerbound          : upperbound != null && value.compareto(upperbound) > 0             ? upperbound             : value; } 

programming note: implementing both iequatable , icomparable ensures type not has inequality semantics may proper graduating range, more implying resetting value/class/struct bound sensible operation. example, series of order statuses might have sequence them, wouldn't make sense constrain orderplaced between ordershipped , orderreturned.

except, compiler resolves following call second overload instead of first:

datetime bounded = new datetime(2016, 1, 1).constrainwithinbounds(    new datetime(2016, 2, 1),    new datetime(2016, 3, 1) ); 

but gives compilation error:

the type 'datetime' must reference type in order use parameter 't' in generic type or method 'constrainwithinbounds<t>(t, t, t)'

correct, datetime non-nullable struct, why overload selection picking wrong one? if name overloads differently, exact same call first overload compiles (and runs) correctly.

what i'd know how have single method group, constraintwithinbounds, job.

now, maybe i'm asking isn't possible, or isn't possible elegantly, i'd know!

test platform suggested answers need cover

here's test class i'm using class type (c# 6.0):

public sealed class myclass : iequatable<myclass>, icomparable<myclass> {    public myclass(int value) { value = value; }    public int value { get; }    public bool equals(myclass other) => other != null && value == other.value;    public int compareto(myclass other) => other == null ? 1 : value.compareto(other.value);    public override bool equals(object obj) => obj != null && value == (obj myclass)?.value;    public override int gethashcode() => value.gethashcode();    public static bool operator ==(myclass a, myclass b) => referenceequals(a, b) || (object) != null && (object) b != null && a.value == b.value;    public static bool operator !=(myclass a, myclass b) => !(a == b);    public override string tostring() => value.tostring(); } 

and unit testing exercise overload resolution , return values:

console.writeline(0.constrainwithinbounds(5, 10) == 5); console.writeline(7.constrainwithinbounds(5, 10) == 7); console.writeline(15.constrainwithinbounds(5, 10) == 10);  console.writeline(testdate.constrainwithinbounds(low, high) == low); console.writeline(testdate.constrainwithinbounds(low, highn) == low); console.writeline(testdate.constrainwithinbounds(low, null) == low); console.writeline(testdate.constrainwithinbounds(lown, high) == low); console.writeline(testdate.constrainwithinbounds(lown, highn) == low); console.writeline(testdate.constrainwithinbounds(lown, null) == low); console.writeline(testdate.constrainwithinbounds(null, high) == testdate); console.writeline(testdate.constrainwithinbounds(null, high) == testdate); console.writeline(testdate.constrainwithinbounds(null, highn) == testdate); console.writeline(testdate.constrainwithinbounds(null, null) == testdate);  console.writeline(testdaten.constrainwithinbounds(low, high) == lown); console.writeline(testdaten.constrainwithinbounds(low, highn) == lown); console.writeline(testdaten.constrainwithinbounds(low, null) == lown); console.writeline(testdaten.constrainwithinbounds(lown, high) == lown); console.writeline(testdaten.constrainwithinbounds(lown, highn) == lown); console.writeline(testdaten.constrainwithinbounds(lown, null) == lown); console.writeline(testdaten.constrainwithinbounds(null, high) == testdaten); console.writeline(testdaten.constrainwithinbounds(null, highn) == testdaten); console.writeline(testdaten.constrainwithinbounds(null, null) == testdaten);  console.writeline(testdate2.constrainwithinbounds(low, high) == high); console.writeline(testdate2.constrainwithinbounds(low, highn) == high); console.writeline(testdate2.constrainwithinbounds(low, null) == testdate2); console.writeline(testdate2.constrainwithinbounds(lown, high) == high); console.writeline(testdate2.constrainwithinbounds(lown, highn) == high); console.writeline(testdate2.constrainwithinbounds(lown, null) == testdate2); console.writeline(testdate2.constrainwithinbounds(null, high) == high); console.writeline(testdate2.constrainwithinbounds(null, highn) == high); console.writeline(testdate2.constrainwithinbounds(null, null) == testdate2);  console.writeline(testdate2n.constrainwithinbounds(low, high) == highn); console.writeline(testdate2n.constrainwithinbounds(low, highn) == highn); console.writeline(testdate2n.constrainwithinbounds(low, null) == testdate2n); console.writeline(testdate2n.constrainwithinbounds(lown, high) == highn); console.writeline(testdate2n.constrainwithinbounds(lown, highn) == highn); console.writeline(testdate2n.constrainwithinbounds(lown, null) == testdate2n); console.writeline(testdate2n.constrainwithinbounds(null, high) == highn); console.writeline(testdate2n.constrainwithinbounds(null, highn) == highn); console.writeline(testdate2n.constrainwithinbounds(null, null) == testdate2n);  console.writeline(my0.constrainwithinbounds(my5, my10).value == 5); console.writeline(my0.constrainwithinbounds(null, my10).value == 0); console.writeline(my0.constrainwithinbounds(my5, null).value == 5); console.writeline(my0.constrainwithinbounds(null, null).value == 0); console.writeline(mynull.constrainwithinbounds(null, null) == null); console.writeline(mynull.constrainwithinbounds(my5, null) == null); console.writeline(mynull.constrainwithinbounds(null, my10) == null); console.writeline(mynull.constrainwithinbounds(my5, my10) == null);  console.writeline(nulldt.constrainwithinbounds(low, high) == null); console.writeline(nulldt.constrainwithinbounds(low, highn) == null); console.writeline(nulldt.constrainwithinbounds(low, null) == null); console.writeline(nulldt.constrainwithinbounds(lown, high) == null); console.writeline(nulldt.constrainwithinbounds(lown, highn) == null); console.writeline(nulldt.constrainwithinbounds(lown, null) == null); console.writeline(nulldt.constrainwithinbounds(null, high) == null); console.writeline(nulldt.constrainwithinbounds(null, highn) == null); console.writeline(nulldt.constrainwithinbounds(null, null) == null);  console.writeline(my7.constrainwithinbounds(my5, my10).value == 7); console.writeline(my7.constrainwithinbounds(null, my10).value == 7); console.writeline(my7.constrainwithinbounds(my5, null).value == 7); console.writeline(my7.constrainwithinbounds(null, null).value == 7); console.writeline(my15.constrainwithinbounds(null, null).value == 15); console.writeline(my15.constrainwithinbounds(my5, null).value == 15); console.writeline(my15.constrainwithinbounds(null, my10).value == 10); console.writeline(my15.constrainwithinbounds(my5, my10).value == 10); 

i know these aren't great unit tests, duplication , all, hey, they'll @ least work prove code doing should...

not real question, further thoughts

p.s. on thought perhaps nullable 2nd , 3rd parameters causing problem, tried adding 1 more overload:

public static t constrainwithinbounds<t>(t value, t lowerbound, t upperbound)    t : struct, iequatable<t>, icomparable<t> {    return value.compareto(lowerbound) < 0       ? lowerbound       : value.compareto(upperbound) > 0          ? upperbound          : value; } 

but causes conflict between 2nd overload , new one:

type 'logichelper' defines member called 'constrainwithinbounds' same parameter types

bonus question: mean overload resolution done on method name , parameters without respect generic type constraints, , later in compilation process type constraints checked?


Comments