Thursday, February 17, 2011

Variable number of results from a function

I have code similar to the following in many places:

var dbParams = db.ReadParams(memberID, product, GetSubscriptionFields());
Debug.Assert(dbParams.Count == 4);

_memberCode = dbParams[0];
_password = dbParams[1];
_userName = dbParams[2];
_reasonCode = dbParams[3];

ReadParams() returns an array of strings, the number of strings depending on the GetSubscriptionFields() function. I could use dbParams[] directly in my code, but I find it more helpful to give meaningful names to each of the values in the array. Is there a way I can get all the results directly, without going through the array?

I am looking for something like:

db.ReadParams(memberID, product, out _memberCode, out _password, out _userName, out _reasonCode);

or

Tuple<_memberCode, _password, _userName, _reasonCode> = db.ReadParams(memberID, product);

Of course, it has to be legal C# code :)

From stackoverflow
  • Why not use constants instead?

    Then in your code you could have

    dbParams[MEMBER_CODE]
    dbParams[PASSWORD]
    dbParams[USERNAME]
    dbParams[REASON_CODE]
    

    which meets your goal of meaningful names without changing the way the method works.

    Marcel Popescu : I like this idea, it's simpler to implement than Mecki's.
  • I think your Tuple idea is pretty good. You could define it like this:

    public class Tuple<T1, T2, T3, T4>
    {
        public T1 Field1 { get; set; }
        public T2 Field2 { get; set; }
        public T3 Field3 { get; set; }
        public T4 Field4 { get; set; }
    }
    

    You would probably want to define a few of those, with two and three properties. Unfortunately it doesn't help you naming the properties of the class. There really is no way to do that (at least not until C# 4.0, when you could use dynamic typing with an anonymous type.

    Marc Gravell : It is still essentially positional, though - i.e. accessing result.Field1 isn't much different from accessing result[0] (assuming they are like-typed)
    Ch00k : You are correct, but there are some other benefits that you get. For example, if you write the appropriate constructor and limit the properties to read only, then you at least get a guarantee that you will get the right number of values back.
  • Not really; since the number of arguments isn't fixed, there isn't really a better way of doing it. The problem with regular tuples is that you are still working positionally - just with ".Value0" instead of "[0]". And anonymous types can't be directly exposed in an API.

    Of course, you could subsequently wrap the values in your own class with properties, and just do a projection:

     return new Foo {MemberCode = arr[0], ...}
    

    (where Foo is your class that represents whatever this result is, with named, typed properties)

    Alternatively you could throw them into a dictionary, but this doesn't help the caller any more than an array does.

    The only other option is something really grungy like accepting a params array of Action<string> that you use to assign each. I'll elaborate on the last just for fun - I don't suggest you do this:

    static void Main()
    {
        string name = "";
        int value = 0;
        Foo("whatever",
            x => { name = x; },
            x => { value = int.Parse(x); });    
    }
    // yucky; wash eyes after reading...
    static void Foo(string query, params Action<string>[] actions)
    {
        string[] results = Bar(query); // the actual query
        int len = actions.Length < results.Length ? actions.Length : results.Length;
        for (int i = 0; i < len; i++)
        {
            actions[i](results[i]);
        }
    }
    
  • You are writing code in a highly object oriented language, so why don't you use objects?

    Member m = db.ReadParams(memberID, product, GetSubscriptionFields());
    

    and in your code you use

    m.memberCode
    m.password
    m.username
    m.reasonCode
    

    Of course you don't have to make the values publicly accessible, you can make them only accessible via setter/getter methods, and by only having getters, you can avoid them from being altered after object creation.

    Of course different calls to db.ReadParams should return different objects, e.g. you can create an abstract base class and inherit all possible results from db.ReadParams of it. Therefor you may have to encapsulate db.ReadParams into another method that finds out the right type of object to create:

    ReadParamsResult rpr = myDb.ReadParamsAsObject(memberID, product, GetSubscriptionFields());
    
    // Verify that the expected result object has been returned
    Debug.Assert(rpr is Member);
    
    // Downcast
    Member m = (Member)rpr;
    
    Marcel Popescu : Very good idea, thanks. For now I'm going with the other one though because it's simpler and faster to implement, but I appreciate this and I might use it later.
  • I also came up with this... I kinda like it most, but it's a personal preference, I understand it's definitely not the cleanest idea:

    using System.Diagnostics;
    
    public static class ArrayExtractor
    {
      public static void Extract<T1>(this object[] array, out T1 value1)
          where T1 : class
      {
        Debug.Assert(array.Length >= 1);
        value1 = array[0] as T1;
      }
    
      public static void Extract<T1, T2>(this object[] array, out T1 value1, out T2 value2)
          where T1 : class
          where T2 : class
      {
        Debug.Assert(array.Length >= 2);
        value1 = array[0] as T1;
        value2 = array[1] as T2;
      }
    }
    

    Of course, I am extending this class up to 10 or 15 arguments.

    Usage:

    string fileName;
    string contents;
    ArrayExtractor.Extract(args, out fileName, out contents);
    

    or even better

    args.Extract(out fileName, out contents);
    

    where args is, of course, an object array.

0 comments:

Post a Comment