Click here to Skip to main content
15,884,298 members
Articles / Programming Languages / C#
Tip/Trick

OperationResult Class in Support of Multiple Return Values from a Method

Rate me:
Please Sign up or sign in to vote.
5.00/5 (18 votes)
14 Dec 2015CPOL4 min read 30.5K   14   10
Sometimes, you just must return multiple values from a method. Instead of specifying out or passing arguments by reference, consider an OperationResult class.

Introduction

As I was watching an interesting Pluralsight class by Deborah Kurata, titled "C# Best Practices: Improving on the Basics", I reminded myself of a recent development team I had a privilege to work with. Few long time team members insisted on adding a never ending number of parameters passed by reference or relying on the out contextual keyword. It was rather a sisyphic job to try to convince them how many SOLID principles and best practices were being violated, how brittle and tightly coupled the code was becoming. I hope some of them will find this tip and realize how much more convenient this approach could have been.

We do often have a need to return a success or failure information as a method's output, along with a message of an encountered error. So at minimum, our OperationResult must provide these two properties, a boolean Success property, and a string Message property. That's exactly what Deborah demonstrated in one of her class segments. Because the number and types of additional return values might vary from one method to another, I have decided to use a thread safe indexed property, with a string key.

C#
public class OperationResult
{
    private readonly ConcurrentDictionary<string, object> _values = 
      new ConcurrentDictionary<string, object>();

    public OperationResult()
    {
    }

    public OperationResult(bool success) : this(success, String.Empty)
    {
    }

    public OperationResult(string message) : this(false, message)
    {
    }

    public OperationResult(bool success, string message) : this()
    {
        Success = success;
        Message = message;
    }

    public int Count
    {
        get { return _values.Count; }
    }

    public object this[string key]
    {
        get { return _values[key]; }
        set { _values[key] = value; }
    }

    public string Message { get; set; }


    public bool Success { get; set; }

    public override string ToString()
    {
        return String.Format("{0}:{1}{2}", Success ? "PASS" : "FAIL", Message,
            Count == 0
                 String.Empty
                : String.Format("({0})", 
                    String.Join(";", _values.OrderBy(o => o.Key)
                          .Select(s => String.Format("{0}:\"{1}\"", s.Key, s.Value)))));
    }
}

I have also decided to overwrite the ToString() method, to provide for better visualization of debug information, as well as to be able to demonstrate the functionality in this tip.

Using the Code

So let's say you have a method, which fails and must return a message of what has failed:

C#
return new OperationResult(false, "Something went wrong.")

That's all it takes and you can test for success or failure in the location the method was called from.

Now, if you wanted to return some data, you would use:

C#
var result = new OperationResult(true, "Data retrieved successfully.");
result["ServerUri"] = "http://www.codeproject.com";
result["Port"] = 80;

return result;

Because the indexed property returns an object type, the burden of unboxing its value, and accounting for associated performance penalty is on the caller.

You can find out more about the boxing and unboxing penalty at this Microsoft website: https://msdn.microsoft.com/en-us/library/ms173196.aspx.

To reduce the impact, assuming a number of your results are of common base type, you could use a generic form of the OperationResult:

C#
public class OperationResult<T> {

private readonly ConcurrentDictionary<string, T> _values =

new ConcurrentDictionary<string, T>();

public OperationResult() { }

public OperationResult(bool success) : this(success, String.Empty) { }

public OperationResult(string message) : this(false, message) { }

public OperationResult(bool success, string message) : this() {
Success = success;
Message = message;
}

public int Count {
get { return _values.Count; }
}

public T this[string key] {
get { return _values[key]; }
set { _values[key] = value; }
}

public string Message { get; set; }

public bool Success { get; set; }

public override string ToString() {
return String.Format("{0}:{1}{2}", Success ? "PASS" : "FAIL", Message,
Count == 0 ? String.Empty :
String.Format("({0})", String.Join(";", _values.OrderBy(o => o.Key)
.Select(s => String.Format("{0}:\"{1}\"", s.Key, s.Value)))));
}
}

And finally, you don't have to be limited to string keys. You could just as easily use an enum, or any other type.

C#
public class OperationResult<TE, TV>
{
    private readonly ConcurrentDictionary<TE, TV> _values = 
      new ConcurrentDictionary<TE, TV>();

    public OperationResult()
    {
    }

    public OperationResult(bool success) : this(success, String.Empty)
    {
    }

    public OperationResult(string message) : this(false, message)
    {
    }

    public OperationResult(bool success, string message) : this()
    {
        Success = success;
        Message = message;
    }

    public int Count
    {
        get { return _values.Count; }
    }

    public TV this[TE key]
    {
        get { return _values[key]; }
        set { _values[key] = value; }
    }

    public string Message { get; set; }

    public bool Success { get; set; }

    public override string ToString()
    {
        return String.Format("{0}:{1}{2}", Success ? "PASS" : "FAIL", Message,
            Count == 0
                ? String.Empty
                : String.Format("({0})", String.Join(";", 
                   _values.OrderBy(o => o.Key)
                          .Select(s => String.Format("{0}:\"{1}\"", s.Key, s.Value)))));
    }
}

Being performance conscious, I notice that I might not need the backing dictionary to be initialized all the time, so to save a few CPU cycles, I can use the Lazy<T> wrapper.

C#
public class OperationResult<TE, TV>
{
    private readonly Lazy<ConcurrentDictionary<TE, TV>> _values = 
	new Lazy<ConcurrentDictionary<TE, TV>>(() => new ConcurrentDictionary<TE, TV>());

    public OperationResult() : this(false, String.Empty)
    {
    }

    public OperationResult(bool success) : this(success, String.Empty)
    {
    }

    public OperationResult(string message) : this(false, message)
    {
    }

    public OperationResult(bool success, string message)
    {
        Success = success;
        Message = message;
    }

    public int Count
    {
        get { return _values.Value.Count; }
    }

    public TV this[TE key]
    {
        get { return _values.Value[key]; }
        set { _values.Value[key] = value; }
    }

    public string Message { get; set; }

    public bool Success { get; set; }

    public override string ToString()
    {
        return String.Format("{0}:{1}{2}", Success ? "PASS" : "FAIL", Message,
            Count == 0
                ? String.Empty
                : String.Format("({0})", 
                    String.Join(";", _values.Value.OrderBy(o => o.Key)
                      .Select(s => String.Format("{0}:\"{1}\"", s.Key, s.Value)))));
    }
}

Looking back at my recent experience, I truly believe that this could have saved us a lot of time and simplified a number of coding challenges. I hope you will find a use for it, too.

And if you are interested in Deborah's class, it is commercially available here (I am only a long-time customer of Pluralsight): https://app.pluralsight.com/library/courses/csharp-best-practices-improving-basics/table-of-contents.

A Follow Up

One of the readers brought up an excellent question: how does this approach compare to passing by reference as well as using the ExpandoObject. I have decided to expand on my testing to find out. Here is the test application code I have used:

C#
class Program
{

    private static ExpandoObject GetAsExpando()
    {
        dynamic rs = new ExpandoObject();
        rs.DerrivedClass = new DerrivedClass { Name = "SomeName" };
        return rs;
    }

    private static DerrivedClass GetAsExpandoResult()
    {
        dynamic rs = GetAsExpando();
        return rs.DerrivedClass;
    }
    
    private static OperationResult<Lookup, object> GetAsObject()
    {
        var rs = new OperationResult<Lookup, object>();
        rs[Lookup.Server] = new DerrivedClass {Name = "SomeName"};
        return rs;
    }

    private static DerrivedClass GetAsObjectResult()
    {
        return (DerrivedClass) GetAsObject()[Lookup.Server];
    }

    private static void GetAsOut(out DerrivedClass result)
    {
        result = new DerrivedClass {Name = "SomeName"};
    }

    private static DerrivedClass GetAsOutResult()
    {
        DerrivedClass dc;
        GetAsOut(out dc);
        return dc;
    }

    private static void GetAsRef(ref DerrivedClass result)
    {
        result = new DerrivedClass {Name = "SomeName"};
    }

    private static DerrivedClass GetAsRefResult()
    {
        DerrivedClass dc = null;
        GetAsRef(ref dc);
        return dc;
    }

    private static OperationResult<Lookup, BaseClass> GetAsSubType()
    {
        var rs = new OperationResult<Lookup, BaseClass>();
        rs[Lookup.Server] = new DerrivedClass {Name = "SomeName"};
        return rs;
    }

    private static DerrivedClass GetAsSubTypeResult()
    {
        return GetAsSubType()[Lookup.Server] as DerrivedClass;
    }

    static void Main(string[] args)
    {
        for (int i = 0; i < 10000; i++)
        {
            var dcObject = GetAsObjectResult();
            var dcSubType = GetAsSubTypeResult();
            var dcRef = GetAsRefResult();
            var dcOut = GetAsOutResult();
            var dcExpando = GetAsExpandoResult();
        }
    }

    class BaseClass
    {
        public string Name { get; set; }
    }

    class DerrivedClass : BaseClass
    {
        public string OtherName { get; set; }
    }
    enum Lookup
    {
        Server,
        Port
    }
}

We have five different ways to get to an instance of the DerrivedClass. First, please note that when you use ref, the argument must be initialized, if even to a null. But when you use out, the argument does not have to be initialized, but the method must assign a value. That is one of the key differences between ref and out.

And here are the performance results, as captured by ANTS Performance Profiler:

All times in milliseconds. The last column is the number of iterations, the one to the left of it is amount of time spent with children. So, when I used out, the program have spent 205ns, on average to get the result. With ref, it was 265ns, using OperationResult<Lookup, BaseClass> took 1.81µs, then with OperationResult<Lookup, object> took 4.54µs on average, and finally, ExpandoObject was most expensive with 94.9µs on average. The test was executed on Windows 10 with dual quad-core Xeon CPUs @ 2.1GHz and 48GB of RAM. 

It was quite an effort to add a new dynamic property as this screenshot indicates.

However, we must put each approach in context. If you were looking for a large prime number or perform other computationally intensive task, the obvious choice is using the out contextual keyword as it yields best performance overall. However, if your network connectivity allways introduces a latency of say 10ms, even the penalty of using ExpandoObject seems to be neglible. 

Thanks

I'd like to thank the readers to bring up interesting questions, which only helped to improve my skills and venture into new territories.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect BI Software, Inc.
United States United States
A seasoned IT Professional. Programming and data processing artist. Contributor to StackOverflow.

Comments and Discussions

 
GeneralGood job! Pin
Emre Ataseven14-Dec-15 10:25
professionalEmre Ataseven14-Dec-15 10:25 
GeneralRe: Good job! Pin
Darek Danielewski14-Dec-15 12:11
Darek Danielewski14-Dec-15 12:11 
QuestionWell-writtten, but a few concerns Pin
BillWoodruff13-Dec-15 21:31
professionalBillWoodruff13-Dec-15 21:31 
AnswerRe: Well-writtten, but a few concerns Pin
Darek Danielewski14-Dec-15 3:35
Darek Danielewski14-Dec-15 3:35 
GeneralRe: Well-writtten, but a few concerns Pin
sx200814-Dec-15 8:09
sx200814-Dec-15 8:09 
GeneralRe: Well-writtten, but a few concerns Pin
Darek Danielewski14-Dec-15 8:45
Darek Danielewski14-Dec-15 8:45 
GeneralRe: Well-writtten, but a few concerns Pin
PIEBALDconsult14-Dec-15 12:34
mvePIEBALDconsult14-Dec-15 12:34 
AnswerRe: Well-writtten, but a few concerns Pin
Darek Danielewski14-Dec-15 5:19
Darek Danielewski14-Dec-15 5:19 
GeneralThoughts Pin
PIEBALDconsult12-Dec-15 19:06
mvePIEBALDconsult12-Dec-15 19:06 
GeneralRe: Thoughts Pin
Darek Danielewski12-Dec-15 19:13
Darek Danielewski12-Dec-15 19:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.