0

I have a method that needs to allow a wide variety of input types. There are two categories of arguments: boundary type arguments, and actual data input arguments.

The boundary arguments are for example, order, frequency, number of points, and number of data points. These boundary arguments are of type int and are common, regardless of the actual data input argument type.

The actual input arguments may be of types: byte, int, short, uint, float, double, decimal, long, etc. To complicate matters further, the actual input data may be individual data, or a list or array of that type. So, the actual input may come as List or uint[], etc. This input is ultimately converted to type double - either as single data or double[].

The method consists of three parts: part one checks the validity of the boundary arguments. This part always applies regardles of the actual input data type. Part two checks and processes the input data arguments. This part changes depending of the type of the input data. Part three performs calculations on the data and is once again common.

I have thought about generic, and I have thought about standard overloading with generics but both seem inefficient. I have come up with what I believe is a workable solution but would appreciate comments: Is my approach computationally efficent, and is there a better way to do this. Your comments would be appreciated.

This is what I currently have:

// ... create lists to store data
static List<double> aList = new List<double>(8);
static List<double> fList = new List<double>(8);

public static double[] MyMethod(int numPts, int numData, object aValue, object fValue)
{
    // ... part 1
    if (numData < 2) throw new ArgumentOutOfRangeException("numberData must be >= 2.");
    if (numPts < 2) throw new ArgumentOutOfRangeException("numberPoints must be >= 2.");
    if (numData < numPts) throw new ArgumentOutOfRangeException("numberData must be
        >= numPts.");

    // ... part 2
    if (aValue is byte || aValue is short || aValue is int || aValue is long ||
        aValue is float || aValue is double || aValue is decimal ||
        aValue is List<byte> || aValue is byte[] || aValue is List<short> ||
        aValue is short[] || aValue is List<int> || aValue is int[] ||
        aValue is List<float> || aValue is float[] || aValue is List<double> ||
        aValue is double[])
    { }
    else throw new ArgumentException("a values must be of a numeric type.");

    double a = 0.0;
    if (aValue is byte || aValue is short || aValue is int || aValue is long ||
        aValue is float || aValue is double || aValue is decimal)
    {
        a = (double)aValue;
        // ... store individual values
        aList.Add(a);

        // ... create the x variable vector
        double[] x = aList.ToArray();     // a values
    }
    else if (aValue is List<byte> || aValue is List<short> || aValue is List<int> ||
             aValue is List<float> || aValue is List<double>)
    {
        // ... get the runtime type of the aValue object
        Type t = aValue.GetType();
        // ... convert the aValue object to a generic list
        IList tmp = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(t));
        // ... convert the generic list to a strongly typed list
        List<double> aValuesList = tmp.Cast<double>().ToList();

        // ... create the x variable vector
        double[] x = aValuesList.ToArray();     // a values
    }
    else
    {
        // ... process the vector input
        // ... get the runtime type of the aValue object
        Type t = aValue.GetType();
        // ... convert the aValue object to an array
        Array tmp = Array.CreateInstance(typeof([]).MakeGenericType(t), aValue.Length);
        // ... convert the generic array to a strongly typed array
        double[] x = tmp.Cast<double>().ToArray();
    }

    // ... part 3
    {
        // ... do calculations
    }
}
Zeos6
  • 273
  • 1
  • 7
  • 18
  • 1
    And what exactly do you have against generics and generic constraints ? – Yochai Timmer Feb 23 '12 at 20:18
  • have you thought about creating perhaps a Delegate .. that's a lot of code by the way...refactor your code.. also where is the return value.. or are you still in the process of coding as you go ... so to speak..? – MethodMan Feb 23 '12 at 20:18
  • I don't think generics can handle this issue, but I could be wrong. – Zeos6 Feb 23 '12 at 20:19
  • I am still coding so it is incomplete. I was concerned about the aprroach and if I need to recode. – Zeos6 Feb 23 '12 at 20:27
  • That's the craziest code i've seen in years... Why are you throwing an exception if aValue is a byte, short, ect. (which will exit your code) then checking it again to see if it is a byte, short, ect. and doing something with it? no wonder it is not working. – tcables Feb 23 '12 at 20:31
  • I did not say it wasn't working. I am testing if it is byte, int, etc. and if not I thrown an exception. Also, I do not know the yupe until runtime so have to convert object to runtime type. – Zeos6 Feb 23 '12 at 20:33

5 Answers5

0

You really don't want to do that.

Since your input is quite complex, and with many checks, you should put all of it into a class that will take care of all the checks. The various types of inputs just beg to become derived classes.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • I though about putting the checks in an abstract class and deriving classes from it, but this is essentially a single method so I don't want a ton of classes. – Zeos6 Feb 23 '12 at 20:21
  • Personally I'd rather have a bunch of classes rather than something as unreadable as that code block, but to each his own. That method is responsible for more than one thing, and that's not good design. – DJ Quimby Feb 23 '12 at 20:24
  • It shouldn't be a single method. – zmbq Feb 23 '12 at 20:24
  • But it essentially is so if I separate it based on input type I will have a huge duplication of the same code. – Zeos6 Feb 23 '12 at 20:28
  • I concur DJ Quimby. That is why I am asking for suggestions for a better approach. – Zeos6 Feb 23 '12 at 20:30
  • If you craft your classes properly, you won't have any duplication of code, just a cleaner division of responsibilities between components. – zmbq Feb 23 '12 at 20:33
0

Just make it accept a double[]. Let the calling code massage their data into the correct format, or provide overloads for the other datatypes.

As an example, if your method is:

public double[] Calculate(double[] aValue, double[] fValue, ...)
{
}

You can provide overloads like:

public double[] Calculate(double aValue, double fValue, ...)
{
    return Calculate(new double[]{aValue}, new double[]{fValue}, ...);
}

public double[] Calculate(IEnumerable<double> aValue, IEnumerable<double> fValue, ...)
{
    return Calculate(aValue.ToArray(), fValue.ToArray(), ...);
}

To cover other datatypes you can reduce the number of overloads by using something like:

public double[] Calculate<T>(T aValue, T fValue) where T : IConvertible
{
    return Calculate(aValue.ToDouble(), fValue.ToDouble(), ...);
}

public double[] Calculate<T>(IEnumerable<T> aValue, IEnumerable<T> fValue) where T : IConvertible
{
    return Calculate(aValue.Select(x=>x.ToDouble()), fValue.Select(x=>x.ToDouble()), ...);
}

This should cover all other primitive data types, which is all of your example.

If you do this, the code in your calculate method is reduced down to:

public double[] Calculate(double[] aValue, double[] fValue, int numData, int numPts)
{
    if (numData < 2) throw new ArgumentOutOfRangeException("numberData must be >= 2.");
    if (numPts < 2) throw new ArgumentOutOfRangeException("numberPoints must be >= 2.");
    if (numData < numPts) throw new ArgumentOutOfRangeException("numberData must be
    >= numPts.");

    // do calculation
}

... which is a lot simpler.

porges
  • 30,133
  • 4
  • 83
  • 114
  • This is not what is wanted; i.e calling code to massage the data. – Zeos6 Feb 23 '12 at 20:38
  • I said do that, *or* provide overloads - so what about overloads? You've said it "seem[s] inefficient", but it's pretty much guaranteed to be faster than using reflection as you are now. – porges Feb 23 '12 at 21:01
  • I am actally strongly leaning that way - to overloads and doing the argument checks in an abstract class. – Zeos6 Feb 23 '12 at 21:13
  • @Zeos6: I've expanded the example. The code should now cover all the cases you wanted, but in a much simpler manner. The main method doesn't need to care about converting arguments. The cases this doesn't cover are those where the arguments are of different types... but you can provide more overloads for that. – porges Feb 23 '12 at 21:14
  • I do like this approacj Porges. I will place the int argument checks into an abstract public method in an abstract class and derive from it. I will then use overloads as you suggested. – Zeos6 Feb 23 '12 at 21:21
  • Should I use IComparable rather than IConvertible? – Zeos6 Feb 23 '12 at 21:29
0

I think a bunch of overloads would be the way to go. Each one would be fairly simple, just calling the main method. But then all of your data type testing is done by the compiler, rather than by your huge if statements. You could also set a flag in each overload to tell your main method if you received a single number, a List<> or an array (or whatever else you need to handle). For example:

enum DataType { SingleNumber, NumberList, NumberArray }

// one of many overloads
public static double[] MyMethod(int numPts, int numData, byte aValue, object fValue) {
  return MyMethod(numPts, numData, (object)aValue, fValue, DataType.SingleNumber);
}
Ray
  • 21,485
  • 5
  • 48
  • 64
0

You have 2 stages there... First you're checking everything, and then aValue separately.

So, separate it.

Then you can use generics to call the specific function for the aValue type

void foo<T> (T aValue) where T : struct { } //value type

void foo<T> (List<T> aValue) where T : struct { } //List of value type

void foo<T> (T[] aValue) where T : struct { } //Array of value type

If you wan a better constraint for numeric types: Generic constraint to match numeric types

Community
  • 1
  • 1
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
0

If you don't want to require the caller to convert its data to a double[] then by all means do it in the callee. But please delegate it to another function! It's much easier to read what's going on:

public static double[] MyMethod(int numPts, int numData, object aValue, object fValue)
{
    //validation omitted for brevity
    // ... part 2
    double[] aValues = ToDoubleArray(aValue);
    // ... the rest
}

Done!

Here's an implementation, though there's doubtless a more robust and/or efficient one:

private double[] ToDoubleArray(object aValue)
{
    if (aValue is byte || aValue is short || aValue is int || aValue is long
        || aValue is float || aValue is double || aValue is decimal)
        return new double[] { Convert.ToDouble(aValue) };

    if (aValue is IEnumerable)
        return ((IEnumerable)aValue).Select(Convert.ToDouble).ToArray();

    throw new ArgumentException("The value was of an unsupported type", "aValue");
}

Done!

phoog
  • 42,068
  • 6
  • 79
  • 117