Method-ology: Getting return values from methods?
Introduction
Since my adoption of C# and .NET back in 2002 and my subsequent attempts to purge my mind of all VB6 corruption, I have grown alot as a developer. However, one seemingly simple question keeps coming back over and over and over again.
"What is the best way to return values from a method?"
So far, my answer has been "There is no one true way", and "It depends upon the situation".
However, even when I seem to address the same types of situations over and over again, my return value decisions often are inconsistent and largely driven by intuition rather than any type of Rule or Best Practice. I am trying to correct this, and develop a good habbits for handling various common situations. Here are some of my thoughts and ramblings on this topic...
Common Return Value Approaches
Using simple a return value is helpful in the most simple situations:
public int DoWork() { ... }
But, what if you need more than 1 value returned? In such cases, I feel tempted to do something like this:
public void DoWork([out] int param1, [out] int param2, [out] int param3) { ... }
This is fine for simple private methods where I will be the only developer consuming the results, but if I am developing an API, I don't like to use Output parameters, so I usually end up with something like this instead:
public int[] DoWork() { ... }
The downside to this is that the developer has to know the ordinal sequence of each returned value. This usually involves creating an Enum or Constants to store the range of values, or the specific definition for each ordinal. This can often be cumbersome, and is more error prone. So, I often do something more like this:
public IWorkResults DoWork() { ... }
OR
public void DoWork(IWorkResults results) { ... }
In this case, I created a results class which implements the IWorkResults interface which will receive the results (think "output parameter").
public class MyWorkResults : IWorkResults
{
public string WorkID;
public string UserID;
public bool Succeeded;
}
The MyWorkResults class defines a named property for each return value. This leads to fewer errors, but a larger payload when you are truly interested in only a single value, such as the Success/Fail flag. Sometimes I use a simple struct instead of the class, or even drop the interface entirely if I know there is no need for polymorphism. There are too many variations on this theme to address here.
Another variation I see alot is a "denormalized" results object-model where the "DoWork()" method and the WorkResults class are combined.
public class MyWorker
{
// Perform work and update local instance variables
// (preferably property-backing variables).
public void DoWork() { ... }
public string WorkID;
public string UserID;
public bool Succeeded;
}
Although this works for the many situations, as I do more class-library development, the API often becomes much more complex as it acquires layers and layers of requirements.
There are plenty more common variations, but lets expand the scope a bit further....
Advanced Scenarios
Often times you need the ability to do more than just get the results of an operation after it is completed. When developing a GUI, you frequently want to show progress during long-running operations by updating a progress bar. This is usually implemented in 2 common ways; Progress Estimation, or Actual Progress.
Progress estimation is usually done by calling the "DoWork()" method asynchronously on a seperate thread from the UI, and blocking until the work is done or some predefined timeout is exceeded. During each "pulse" of the thread, you check to see if the work is done, and update the progress bar to using % of time elapsed between Zero and the Timeout Period. When work completes quickly, you often will see the progress bar "leap" straight to 100% from 20%.
Estimation is satisfactory for many operations, but in some cases you want "Actual Progress". One complex variation of displaying Actual Progress is when you want to display textual messages as soon as a step in a multi-step or long-running operation completes.
Lets look at a real-world application that most VisualStudio.NET developers use; Visual SourceSafe(VSS) Explorer. The VSS Explorer tool is a Windows application that allows you to perform commands on a Visual SourceSafe repository. Each command appears to be executed asynchronously and gives a response in the "output" window at the bottom of the screen.
In the above screenshot, the user performs a "Get latest version..." on a project folder, and each item is recursively retrieved. As each item is retrieved, text is displayed in the bottom "output" window showing the results.
Imagine that you are developing this application in .NET and needed to solve the same problems.
In such cases, you need a mechanism to get feedback from your internal API that is doing the work rather than wrapping it with a thread monitor. To get realtime progress, you usually follow one of (at least) 2 different approaches; Eventing or Callbacks. These are closely related, but used in different scenarios.
The eventing model is something that both older VB6 programmers and newer .NET programmers should be familiar with. The VB6, and .NET WinForm and ASP.NET frameworks are heavy adopters of this approach. With eventing, you expose a public event on your instance class, to which callers can subscribe and listen for response events. In the case of VSS, you could create a class library wrapping all of the commands against the VSS Engine, and expose an Event to allow the UI to listen and display the results.
public class VssWrapper
{
// Connects to VSS DB and stores reference in instance variable.
public void Connect(string srcSafePath, string userID, string password) {...}
// Performs Vss GET and fires CommandResponse event per iteration.
public void GetLatestVersion() { ... }
public delegate void VssResponseEventHandler(string result, bool succeeded);
public event VssResponseEventHandler CommandResponse;
}
However, what if you wanted to use this VssWrapper class in a library outside of the GUI? Consider the idea of a command-line utility. You can still create an instance of VssWrapper and use the eventing model, but since command-line utilities rarely maintain state between calls, you can make the API simpler for the developer by adding a simple Static method that is stateless and responds via a Callback delegate.
public class VssWrapper
{
// Connects to VSS DB and stores reference in instance variable.
public void Connect(string srcSafePath, string userID, string password) {...}
// Performs Vss GET and fires CommandResponse event per iteration.
public void GetLatestVersion(string vssPath, string workingFolder) { ... }
public delegate void VssResponseEventHandler(string result, bool succeeded);
public event VssResponseEventHandler CommandResponse;
// Connects, Performs GET, and fires a VssResponseEventHandler callback per iteration.
public static void GetLatestVersion(string srcSafePath,
string userID,
string password,
string vssPath,
string workingFolder,
VssResponseEventHandler responseCallback) { ... }
}
Such API design decisions provide yet another way to provide realtime insight into the actual progress of the operation. Keep in mind, this isnt the only solution, but just another illustration of a way to get data back from a method call.
Conclusion
Although I havent even scratched the surface on the many possible permutations, you can see that identifying the correct approach for returning data from your applications can vary from the simple to the complex, and each is dictated by your specific scenario.
So far, I have used an intuitive "approach" to decide how to handle method output, but I still do not see any hard and fast "rules" to guide developers when making such decisions. Is this where art meets science, or am I just ignorant of existing research? I would guess it is the latter, but would appreciate help if anyone can point me in the direction of said research material. Having said that, I know many many developers who fight these same basic programming issues every day. I bet that some of you coding guru's out there will likely have some great nuggets of information tucked away that you could share with us relative newbies. All I can say is "Bring it on!".
At this point, I'll leave you with some questions;
What useful rules-of-thumb or guidelines have you developed for returning data?
Can you recommend a book, website, or other resource for learning more on such strategies?
Where should I go from here?
Thank you for reading my ramblings!