简体   繁体   English

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

[英]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说,我有以下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
{
    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;

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.我首先将Int检查从 get 移动到 set 属性。 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.如果它不应该小于 0,那么不要让它设置为小于 0。对于日期,在设置器中使用TryParse方法以使其异常安全。 Then make sure that use a private setter in the BirthDay property.然后确保在 BirthDay 属性中使用私有设置器。

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):他们将执行空值检查(通过 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));
    }

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?如果您不需要能够存储无效的生日字符串,那么我BirthDay设为正常的DateTime? or DateTime property.DateTime属性。 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.这是一个选项 - 不要在属性分配中抛出基本异常 class,然后将分配包装在可以提供默认值的方法中。

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.属性 getter 应该是简单的操作,不应该有前置条件。 If a getter can throw an exception, it should probably be redesigned to be a method.如果 getter 可以抛出异常,它可能应该重新设计为方法。 Note that this rule does not apply to indexers, where we do expect exceptions as a result of validating the arguments.请注意,此规则不适用于索引器,我们确实希望在验证 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.他们不想要执行复杂计算或需要大量 CPU 时间才能完成的 getter。 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.在大多数情况下,BCL 和其他所有人都遵循此指南,建议您也这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM