0

I have a method as follows:

int coerce(int val, int min=0, int max = 10)
{
    if (val < min)
        return min;
    if (val > max)
        return max;
    return val;
}

Now, I have to make it for byte, float, double, and other numeric types.

As all we know, making numerous similar methods for those types is very ineffective, so I want to make it into a generic method. The following is the what I tried to do:

T coerce<T>(T val, T min=(T)0, T max=(T)10) where T:IComparable
{
    // ... same as the above ...
}

I know that the code does not run, and that's why I'm asking for this. I'm currently confused by two questions:

  1. How can I compare T types?

    Visual Studio warns about the operator < and >. I tried to use where T:IComparable but it did not solve my problem.

  2. How can I set default values for a generic argument?

    I tried to use (T)0 and (T)10 for it. But it was not a good choice, anyway.

It may be a simple question, but I couldn't find answer from Google.

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
J.-H. Bak
  • 15
  • 4
  • Did you look at the msdn example ? https://learn.microsoft.com/en-us/dotnet/api/system.icomparable-1?view=netframework-4.8 – jdweng Apr 19 '20 at 09:58
  • 2
    `IComparable` defines `CompareTo`, not the comparison operators. So instead of `val < min` for instance, you need to use `val.CompareTo(min) == -1` – vc 74 Apr 19 '20 at 09:59
  • ... and you cannot use `int` default values for your generic implementation, the best you could do is `int min = default` but IMO you should not try to implement default values in this method. – vc 74 Apr 19 '20 at 10:01
  • How can I set default values for generic type argument? - You can use the `Default(T)` which sets it to the default value of the datatype. In your case 0 for integer. – Denzil Soans Apr 19 '20 at 10:05
  • Thanks for the help guys, now Q1 is very clear to me. But for Q2, default value of the variable 'min' may not be 0. So I can't use ```Default(T)```. – J.-H. Bak Apr 19 '20 at 10:18
  • Guys, for Q2, I have an idea to solve the problem. ```T coerce(T val, T? min, T? max) where T:struct``` And now I just check ```min == null``` and ```max == null```. But I can't assign default value such as ```if(min == null) max = (T)10```. Any ideas to solve this last problem? – J.-H. Bak Apr 19 '20 at 10:31
  • 1
    I think [`Clamp()`](https://en.wikipedia.org/wiki/Clamping_(graphics)), not `coerce()`, would be a more appropriate and well-known name for this method. With that in mind, many of the answers to [Where can I find the “clamp” function in .NET?](https://stackoverflow.com/q/2683442/150605) provide a generic implementation of this. – Lance U. Matthews May 14 '20 at 20:28
  • Thanks for your tip, @BACON ! Actually, I'm not a native English speaker. So these kinds of tips are so valuable for me. Thanks again :) – J.-H. Bak May 21 '20 at 05:22

3 Answers3

2

You should use Compare method instead of < or > operators. And apply the correct generic constraints, for numeric types it should be IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable. However, you can leave only where T : struct, IComparable<T>, it should be enough for your purposes (but struct is important here, since you are comparing value types)

T coerce<T>(T val, T min, T max) where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
{
    if (val.CompareTo(min) < 0)
        return min;
    if (val.CompareTo(max) > 0)
        return max;

    return val;
}

You can also specify the default min value like T min = default(T), but you can't do that for max value.

Following the comments, in case of using Nullable<T> for min and max values the code can be written like

T coerce<T>(T val, T? min = default, T? max = default) where T : struct, IComparable<T>
{
    var minValue = min.HasValue ? min.Value : default(T);
    var maxValue = max.HasValue ? max.Value : (T)Convert.ChangeType(10, typeof(T));

    if (val.CompareTo(minValue) < 0)
        return minValue;
    if (val.CompareTo(minValue) > 0)
        return maxValue;

    return val;
}
Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
  • Thanks, for your help. Q1 is very clear now for me. But still, Q2 is a problem.. ```min``` could not be 0, so I can't use default(T) The code above is just a simplified example of mine. Anyway, thanks for the help!! – J.-H. Bak Apr 19 '20 at 10:21
  • @J.-H.Bak you can use a reflection to get and `Min` and `Max` values of `T`, it's explained in this [answer](https://stackoverflow.com/a/4418645/4728685), But it looks like an overkill here, IMO. Specifying a explicit values directly is more clear and readble – Pavel Anikhouski Apr 19 '20 at 10:30
  • Thanks for the comment. I agree with that it is a waste. Here I bring a new idea for assigning default value for input parameters. But still need one step. Can I ask for just one more help to complete this idea? ```T coerce(T val, T? min, T? max) where T:struct ``` and then ```if(max == null) max = (T)10```. But now I can't assign the value for it. – J.-H. Bak Apr 19 '20 at 10:38
  • 1
    @J.-H.Bak `T` can be any numeric value, not only the `int`, so `max = (T)10` isn't valid, unfortunately. You may use `Convert.ChangeType` for that. In case of nullable `T` you'll need to access the `Value` property of it – Pavel Anikhouski Apr 19 '20 at 10:52
0
public T Max<T>(T val, T min, T max) where T : IComparable<T>
{
    if (val.CompareTo(min) == -1)
        return min;

    if (val.CompareTo(min) == 1)
        return max;

    return val;
}

Detail

Ekrem Kangal
  • 105
  • 1
  • 2
  • 10
  • Thanks for your valuable help. :) Now I realize that I completely misunderstood ```IComparable``` thing. – J.-H. Bak Apr 19 '20 at 10:25
0

The trick is use the IComparable methods, not the compare operators. In this case the CompareTo method

Default values for parameters is not easy, you are supposing numeric values and doing direct cast, but thing that T can be any IComparable data type

public static T Coerce<T>(T val, T min, T max) where T:IComparable {
    if (val.CompareTo(min) < 0)
        return min;
    else if (val.CompareTo(max) > 0)
        return max;
    else
        return val;
}

Fiddle: https://dotnetfiddle.net/fJUDdR

mnieto
  • 3,744
  • 4
  • 21
  • 37