在C#中尝试捕获属性的优雅方法是什么?

时间:2011-06-08 15:22:06

标签: c# refactoring try-catch

重构此代码的优雅方法是什么?

说,我有以下对象

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); } }
    }
}

并说,我必须设置一些我需要设置的文本框值

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

//These will pass but it looks ugly
try
{
    textbox1.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;

有更好的方法吗?

谢谢,

6 个答案:

答案 0 :(得分:6)

当我真的不关心特定代码行会发生什么时,我会这样做:

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

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

答案 1 :(得分:2)

我首先将Int检查从get转移到set属性。如果它不应该小于0,那么不要让它设置为小于0.对于日期,在setter中使用TryParse方法使其异常安全。然后确保在BirthDay属性中使用私有的setter。

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;
            }
        }
    }
}

重点是价值应该在途中而不是在出路上进行验证。

答案 2 :(得分:1)

尝试这些扩展方法;他们将执行空值检查(通过try-catch)并返回一个默认值(如果你不想要.NET默认值,你可以指定):

    /// <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));
    }

用法:

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

答案 3 :(得分:1)

如果您不需要存储无效的生日字符串,那么我会将BirthDay设为正常DateTime?DateTime属性。然后解析输入值并将解析结果分配给属性。

如果你真的需要存储字符串,可以将生日情况重写为:

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

答案 4 :(得分:0)

这是一个选项 - 不要在属性赋值中抛出基本Exception类,然后将赋值包装在可以提供默认值的方法中。

快速和肮脏的东西,如:

//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;
    }
}

答案 5 :(得分:0)

我会尽力避免从属性getter中抛出异常。框架设计指南说:

  

避免抛出异常   财产瘾君子。物业吸气剂   应该是简单的操作和应该   没有先决条件。如果是一个吸气剂   它应该抛出异常   可能会被重新设计成一种方法。   请注意,此规则不适用于   索引器,我们期待的地方   验证的例外情况   争论。请注意这一点   准则仅适用于财产   干将。抛出一个是可以的   属性设置器中的异常。

开发人员已经习惯于几乎在所有时间都能安全地呼叫财产。他们不希望getter执行复杂的计算或需要大量的CPU时间来完成。他们当然希望他们不会抛出异常。在大多数情况下,BCL和其他所有人都遵循本指南,建议您也这样做。