开发者

What is an elegant way to try catch properties in C#

What's an elegant way to refactor this code?

Say, I have the following object

public class SomeObject
{
    public SomeInnerObject1 Cat { get; set; }
    public SomeInnerObject2 Dog { get; set; }

    public class SomeInnerObject1
    {
        public int Age { get; set; }

        public string AgeAsString
        {
            get
            {
                if(Age < 0 )
                    throw new Exception();

                return Age.ToString();
            }
        }
    }

    public class SomeInnerObject2
    {
        public string BirthDayString { get; set; }
        public DateTime BirthDay { get { return DateTime.Parse(BirthDayString); } }
    }
}

And say, I have to set a few textboxes's values that I need to set

var obj = new SomeObject
          {
              Cat = new SomeObject.SomeInnerObject1 {Age = -1},
              Dog = null
          };

//These will pass but it looks ugly
try
{
    textbox开发者_StackOverflow社区1.Text = obj.Dog.BirthDay.Month.ToString();
}
catch{ }

try
{
    textbox2.Text = obj.Cat.AgeAsString;
}
catch { }

//These will fail
textbox1.Text = obj.Dog.BirthDay.Month.ToString();
textbox2.Text = obj.Cat.AgeAsString;

Is there a better way to do this?

Thanks,

Chi


When I really don't care what happens for a particular line of code, I do something like this:

ExecuteIgnoringExceptions(() => textBox.Text = Some.Possibly.Bad.Value);

void ExecuteIgnoringExceptions(Action a) 
{
    try
    {
        a.Invoke();
    }
    catch
    {
    }
}


I would start by moving the Int check from the get to the set property. If it isn't supposed to be less than 0, then don't let it get set to less than 0. For the date, use a TryParse method in the setter to make it exception safe. Then make sure that use a private setter in the BirthDay property.

public class SomeObject
{
    public SomeInnerObject1 Cat { get; set; }
    public SomeInnerObject2 Dog { get; set; }

    public class SomeInnerObject1
    {
        private int _Int = 0;
        public int Int {
            get
            {
                return _Int;
            }
            set
            {
                if(value < 0) 
                    throw new Exception("Int must be greater than or equal to 0.");
                else
                    _Int = value;
            }
       }

        public string String
        {
            get
            {
                return Int.ToString();
            }
        }
    }

    public class SomeInnerObject2
    {
        private string _BirthDayString = "";
        public string BirthDayString
        {
            get
            {
                return _BirthDayString;
            }
            set
            {
                DateTime newDate;
                if(DateTime.TryParse(value, newDate))
                    BirthDay = newDate;
                else
                    throw new ArgumentException("Birthday string must be a properly formatted date.");
            }
        }

        private DateTime _BirthDay = DateTime.MinValue;
        public DateTime BirthDay
        {
            get 
            {
                return _BirthDay;
            }
            private set
            {
                _BirthDay = value;
            }
        }
    }
}

The main point being that values should be validated on the way in rather than on the way out.


Try these extension methods; they will perform the null-checking (via try-catch) and return a default value (which you can specify if you don't want the .NET default):

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the specified default value.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <param name="defaultValue">The default value to use if the projection or any parent is null.</param>
    /// <returns>the result of the lambda, or the specified default value if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, TOut defaultValue)
        where TOut : class
    {
        try
        {
            return projection(input) ?? defaultValue;
        }
        //Catches attempts to access a child of a null object
        catch (NullReferenceException)
        {
            return defaultValue;
        }
        //Catches attempts to access the value of a null Nullable<T>
        catch (InvalidOperationException)
        {
            return defaultValue;
        }
    }

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the default value for the type.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <returns>the result of the lambda, or default(TOut) if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection)
        where TOut : class
    {
        return input.ValueOrDefault(projection, default(TOut));
    }

Usage:

//no try-catch needed
textbox1.Text = obj.ValueOrDefault(o=>o.Dog.BirthDay.Month.ToString(), String.Empty);


If you don't need to be able to store invalid birthday strings, then I'd make BirthDay a normal DateTime? or DateTime property. And then parse the input value and assign the result of the parsing to the property.

If you really need to store the string you could rewrite the birthday case to:

public DateTime? BirthDay
{
  get
  {
    DateTime result;
    if(DateTime.TryParse(BirthDayString,out result))
      return result;
    else
      return null;
   }
}


Here is one option - Don't throw the base Exception class in your property assignments, and then wrap the assignments in a method that can provide a default value.

Something quick and dirty like:

//assignment will throw
var x = SafeAssign(()=>((string)(null)).ToString(),"test");

T SafeAssign<T>(Func<T> input, T defaultValue)
{
    try
    {
        return input();
    }
    catch //todo - catch better exceptions
    {
        return defaultValue;
    }
}


I would try really hard to avoid throwing exceptions from property getters. The Framework Design Guidelines say:

AVOID throwing exceptions from property getters. Property getters should be simple operations and should not have preconditions. If a getter can throw an exception, it should probably be redesigned to be a method. Note that this rule does not apply to indexers, where we do expect exceptions as a result of validating the arguments. Note that this guideline only applies to property getters. It is OK to throw an exception in a property setter.

Developers have grown accustomed to being able to call property getters safely almost all of the time. They do not want a getter that performs complex computations or requires a lot of CPU time to complete. And they certainly would prefer that they not throw exceptions. For most part the BCL and everyone else follow this guideline and it is advisable that you do as well.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜