|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionI really don't like the C# History13 July, 2006
12 July, 2006
ContentsWhat problem am I trying to solve?It's probably best to show an example of where the int value;
if (Int32.TryParse("123", out value)) {
// we have a real integer
}
// or the more natural (but exception prone) way
value = Int32.Parse("123");
Personally, I feel that the last line looks much cleaner, neater, easier to read and more to the point. You are calling a method that returns a value (just like 90% of the .Net framework methods do) and your variable will be set or an exception is thrown If you have a method that should return a The int value;
if (!Int32.TryParse(txtAge.Value, out value)) {
// ok, we failed to parse the input but why????
}
Wouldn't it be nice if you could pass a message back like "the value you supplied was too big" or "the value contained letters" instead of giving the user a catch all message like "sorry that does not compute". Of course, we can do this by throwing an exception, but it's a performance bottle kneck and it forces the consumer of your method to write exception handlers (which I suspect is why Microsoft created the TryParse() method instead).
So, we can sum up the problem by specifying what goals our solution should have. It should:
SolutionWe can provide a reusable generic solution to this problem by making use of generics, type converters and anonymous methods. If we can return a reusable rich object which allows the consumer to get the value, message or exception and still write code that looks natural then I think we're on to a winner. My /// <summary>
/// Resuable generic result class which allows a value, exception
/// or message information to be passed back to a
/// calling method in one go.
/// </summary>
/// <typeparam name="T">The expected return type</typeparam>
public class Result<T> {
protected T result = default(T);
protected string message = string.Empty;
protected Exception exception = null;
protected Predicate<T> boolConverter = delegate { return true; };
public Result(T result) : this(result, string.Empty, null, null) {}
public Result(T result, Predicate<T> boolConverter) : this(result, string.Empty, null, boolConverter) { }
public Result(T result, string message) : this(result, message, null, null) { }
public Result(Exception exception) : this(default(T), string.Empty, exception, null) { }
public Result(T result, string message, Exception exception, Predicate<T> boolConverter) {
this.result = result;
this.exception = exception;
if (exception != null && message == string.Empty)
this.message = exception.Message;
else
this.message = message;
if(boolConverter!= null)
this.boolConverter= boolConverter;
}
public T ActualResult {
get { return this.result; }
}
public string Message {
get { return this.message; }
}
public Exception Exception {
get { return this.exception; }
}
public bool Success {
get { return CheckForSuccess(this); }
}
public static explicit operator bool(Result<T> result) {
return CheckForSuccess(result);
}
static bool CheckForSuccess(Result<T> result) {
return result.Exception == null &&
string.IsNullOrEmpty(result.message) &&
result.boolConverter(result.ActualResult);
}
public static implicit operator T(Result<T> result) {
if (result.Exception != null)
throw result.Exception;
if(!result.boolConverter(result.ActualResult))
throw new Exception("Result failed the boolConverter test and is not guaranteed to be valid");
return result.ActualResult;
}
}
How to use itThe first thing to notice is that the class takes a generic parameter when you define an instance of it. If we were to write a wrapper around the public Result<int>ParseInt(string sValue) {
int value;
if (!Int32.TryParse(sValue, out value)) {
// more investigation required into why it failed here
return new Result<int>(new Exception("String couldn't be converted to an integer"));
}
return new Result<int>(value);
}
How does this work from the calling method's side?This is where the pattern really kicks in. The consumer can now decide how they want to use your method. There are no less than two ways to call the Method 1 (the 'confident' method): Treat the return result as a normal value public void DoSomething(string sValue) {
// the ParseInt method will throw an exception if the string wasn't successfully parsed
int value = ParseInt(sValue);
}
Method 2 (the 'defensive' method): Treat the return result as a result object public void DoSomething(string sValue) {
// the ParseInt method will throw an exception if the string wasn't successfully parsed
Result<int> value = ParseInt(sValue);
if((bool)value) {
// get the int out of the result
int intValue = value.ActualValue;
} else {
// tell everybody why it failed
MessageBox.Show(value.Message);
// or you could do this
// throw value.Exception;
}
}
Anonymous MethodsSay we wanted to extend the public Result<int>ParseInt(string sValue) {
int value;
if (Int32.TryParse(sValue, out value)) {
// more investigation required into why it failed here
return new Result<int>(new Exception("String couldn't be converted to an integer"));
}
return new Result<int>(value,
delegate(T obj) {
if (Convert.ToInt32(obj) > 0)
return true;
return false;
};
);
}
The result is now only valid when the value is greater than zero.
Type ConvertersExplicit Implicit It might be beneficial to extend the Result class for particular datatypes. I know that this goes some way against the whole generics idea but in doing so could allow more specialised results to be put in place. For example, if the result is always numeric then a sub class could be written that extends Result Adding these sort of helper methods nicely encapsulates the logic of the class - yet still allows it to be extended. I hope that somebody out there finds this simple class useful. I have used the pattern in a number of large projects and it's been extremely useful to me. I'd love to hear from anybody who uses the class and especially those who extend it.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||