views:

249

answers:

7

I have a method like this:

private static double ComputePercentage(ushort level, ushort capacity)
{
      double percentage;
      if(capacity == 1)
        percentage = 1;

      // do calculations...
      return percentage;
}

Is it possible to make it of a generic type like "type T" where it can return either decimal or double, depending on the type of method expected (or the type put into the function?)

I tried something like this and I couldn't get it to work, because I cannot assign a number like "1" to a generic type. I also tried using the "where T :" after ushort capacity) but I still couldn't figure it out.

private static T ComputePercentage<T>(ushort level, ushort capacity)
{
      T percentage;
      if(capacity == 1)
        percentage = 1; // error here

      // do calculations...
      return percentage;
}

Is this even possible? I wasn't sure, but I thought this post might suggest that what I'm trying to do is just plain impossible.


EDIT

Thanks to all who responded, many good answers. As Tomas pointed out, this is probably best done in two separate methods. As pointed out by both TreDubZedd and TcKs, the best way to get the functionality I would want is to use an implicit conversion that could return either a double or decimal implicitly.

+3  A: 

You might be able to use implicit conversion: http://msdn.microsoft.com/en-us/library/zk2z37d3.aspx

TreDubZedd
A: 
private static T ComputePercentage<T>(ushort level, ushort capacity)
{
      if (typeof(T) == typeof(decimal))
        {
            decimal percentage = 1;
            return (T) (object) percentage;
        }

        if (typeof(T) == typeof(double))
        {
            double percentage = 1;
            return (T) (object) percentage;
        }

      throw new NotSupportedExcpetion();
}
bitbonk
while this would work, it would require a large amount of code duplication - the actual application has more than just the `double percentage = 1;` or `decimal percentage = 1;
CrimsonX
A: 

It's not pretty, but try:

percentage = (T)Convert.ChangeType(1, typeof(T));

This works for both double and decimal at least.

Chris Schmich
A: 

If you're using C# 4.0 you could just return a dynamic.

Esteban Araya
+3  A: 

Generics are useful for writing code that works with any types (perhaps implementing some interface, which can be specified using where). However, if you want to use them for implementing method that can return two different numeric types, it feels a bit wrong (it would work only if double and decimal implemented some shared interface).

You should probably define two distinct methods (e.g. ComputePercentage and ComputePercentagePrecise or something like that - since you can't use overloading using different parameters).

It may be possible to workaround this limitation by using something like this (but that's probably overly complicated for you):

class INumericOps<T> {
  public abstract T One { get; }
  public abstract T Add(T a, T b); 
  // and other operations that you may need
}

// Implementations of numeric operations for decimal and double
class DecimalNumerics : INumericOps<decimal> { ... }
class DoubleNumerics : INumericOps<double> { ... }

Then you would write a method that takes INumericOps<T> as a type parameter and uses it to do all mathematics inside the method:

private static R ComputePercentage<T, R>(ushort level, ushort capacity) 
  where T : INumericOps<R>, where T : new() {
  INumericOps<R> ops = new T(); // Get instance with numeric operations
  T percentage;  
  if(capacity == 1)  
    percentage = ops.One; 

  // Calculations using ops.Add(.., ..) instead of + etc.
  return percentage;  
}  

Then you would call it like this:

decimal res = ComputePercentage<DecimalNumerics, decimal>(...);

This is a nice trick and it is probably the best (type-safe) workaround you can get. However, it is a bit complicated, so declaring two separate methods may be a better idea.

Tomas Petricek
I think that you're right about just creating two simple methods - my main goal was to just save duplication of code, but this isn't a critical concern and is a nice to have
CrimsonX
+4  A: 

In fact, you don't need generics but overloading. However you need overloading by return value's type which is supported by IL but is not supported by C#.

I prefere two methods for every return's value type:

static double  ComputePercentageDouble  (ushort level, ushort capacity)
static decimal ComputePercentageDecimal (ushort level, ushort capacity)

The alternative can be custome type with implicit cast operators:

decimal decimalPercentage = ComputePercentage( 1, 2 );
double  doublePercentage  = ComputePercentage( 1, 2 );

static DoubleDecimal ComputePercentage( ushort level, ushort capacity ) {
    DoubleDecimal percentage = default( DoubleDecimal );
    if ( capacity == 1 )
        percentage.Number = 1; // error here

    // do calculations...
    return percentage;
}


public struct DoubleDecimal {
    public decimal Number;

    public static implicit operator decimal( DoubleDecimal value ) {
        return value.Number;
    }
    public static implicit operator double( DoubleDecimal value ) {
        return (double)value.Number;
    }
}
TcKs
A: 

Why not create a Percent class?

class Percent
{
    public Percent(double value)
    {
        this.value = value;
    }

    public double AsDouble()
    {
        return value;
    }

    public decimal AsDecimal()
    {
        return (decimal)value;
    }

    readonly double value;
}

static Percent ComputePercentage(ushort level, ushort capacity)
{
    double percentage = 0;
    if (capacity == 1)
    {
        percentage = 1;
    }

    // calculations

    return new Percent(percentage);
}
Sam Pearson
Because you've just trashed your precision, which was the whole point of using Decimal to begin with.
Ben Voigt
I don't understand. Decimal was not used to begin with, double was. Also, assuming the OP intended to use decimal to store the percent, why can't we just change the Percent ctor to decimal? Then AsDouble gets the cast instead of AsDecimal.
Sam Pearson