【问题标题】:in-line 'try' statement in C# possible?C# 中的内联“尝试”语句可能吗?
【发布时间】:2013-01-25 06:05:48
【问题描述】:

是否可以在 C# 中以某种方式调用内联 try 语句?

我正在为我的网站检测语言,有时,由于某种原因,当客户端的语言类似于 en-GR 时,.NET 会引发异常。所以我需要使用trycatch,即使我没有真正捕捉到任何东西。

在这种情况下,这似乎完全是矫枉过正。

// Set allowed languages
string[] allowedLanguages = { "en", "fr", "ru" };

// Get all possible values
var routeLanguage = (filterContext.RouteData.Values["lang"] != null && allowedLanguages.Contains(filterContext.RouteData.Values["lang"].ToString())) ? filterContext.RouteData.Values["lang"].ToString() : null;
var cookieLanguage = (filterContext.HttpContext.Request.Cookies["lang"] != null && allowedLanguages.Contains(filterContext.HttpContext.Request.Cookies["lang"].Value)) ? filterContext.HttpContext.Request.Cookies["lang"].Value : null;
string clientLanguage = null;
try
{
    clientLanguage = (filterContext.HttpContext.Request.UserLanguages != null) ? new CultureInfo(filterContext.HttpContext.Request.UserLanguages[0]).TwoLetterISOLanguageName : null; // Exception sometimes without `try`
}
catch (Exception)
{
}

编辑

我无法修复异常,因为我无法控制用户在其文化信息中的内容。 .NET 只是将 en-FR 视为无效的。

【问题讨论】:

  • 内联尝试是什么意思?
  • 抛出了什么异常?
  • 不。你以错误的方式处理这个问题。修复你的错误,而不是试图让它静音!
  • 异常不是我可以修复的,因为我无法控制用户在其文化信息中的内容。 .NET 只是将en-FR 视为无效的。
  • @Steve 不,你可以修复它。

标签: c# exception exception-handling try-catch


【解决方案1】:

这是另一种方式:

await new @try(async () => { user = await GetItemAsync(userId); })
         .@catch<Exception>(async () => { user = new User(); });

我认为它比 try/catch 更冗长:

try { return await GetItemAsync(userId); } 
catch (Exception ex) { return new User(); }

助手类:

public class @try
{
    private Func<Task> func;
    private Action action;

    public @try(Func<Task> func)
    {
        this.func = func;
    }

    public @try(Action action)
    {
        this.action = action;
    }

    public async Task @catch<T>(Func<Task> fallback) where T : Exception
    {
        try
        {
            await func.Invoke();
        }
        catch(T ex)
        {
            await fallback.Invoke();
        }
    }

    public void @catch<T>(Action fallback) where T : Exception
    {
        try
        {
            action.Invoke();
        }
        catch (T ex)
        {
            fallback.Invoke();
        }
    }
}

【讨论】:

  • 灵感来自@Andy 课程
【解决方案2】:

我已经构建了一个可能适合此目的的内嵌 try catch 机制。我想要一种类似 LINQ 或 Callback function-y 的语法。这是使用几个包装器,一个 TryWrapper 和一个 CatchWrapper 来完成的,以便点运算符适当地提示下一步,隐式转换为 T 类型。

你也可以这样做

Try(() => UpdateSweetGreen("21", SweetGreen))
.Catch(LogToDb(e.Message))
.Catch(LogToFile(e.Message).Finally(ReportNewSweetGreen(SweetGreen);

基本上,CatchWrapper 扩展了 TryWrapper。因此,您可以从另一个 catch 块中捕获异常。在这种情况下,将您的方法失败记录到数据库中,然后如果失败记录到文件中,那么无论将 SweetGreen 变量报告给其他组件。

这一切都是从 TryWrapper 扩展而来的

    public class TryWrapper<T>
    {
        protected internal T Result { get; set; } = default(T);

        protected internal Exception Exception { get; set; } = null;

        public static implicit operator T(TryWrapper<T> wrapper)
        {
            return wrapper.Result;
        }

        public static implicit operator Exception(TryWrapper<T> wrapper)
        {
            return wrapper.Exception;
        }
    }

和 CatchWrapper,它只是扩展 TryWrapper 并且不能直接调用,而是仅在尝试后出现,正如您在标准实现中所期望的那样

    public class CatchWrapper<T> : TryWrapper<T>
    {
    }

然后是一系列静态扩展方法

        public static TryWrapper<T> Try<T>(Func<T> func)
        {
            var product = new TryWrapper<T>();

            try
            {
                product.Result = func.Invoke();
            }
            catch (Exception e)
            {
                product.Exception = e;
            }

            return product;
        }

        public static TryWrapper<T> Try<T>(Action action)
        {
            var product = new TryWrapper<T>();

            try
            {
                action.Invoke();
            }
            catch (Exception e)
            {
                product.Exception = e;
            }

            return product;
        }

        public static CatchWrapper<T> Catch<T>(this TryWrapper<T> wrapper, Action<Exception> response)
        {
            if (wrapper.Exception is null) return wrapper as CatchWrapper<T>;

            response.Invoke(wrapper);
            wrapper.Exception = null;

            return wrapper as CatchWrapper<T>;
        }


        public static TryWrapper<T> Finally<T>(this TryWrapper<T> wrapper, Action<T> response)
        {
            response.Invoke(wrapper);

            return wrapper;
        }

        public static TryWrapper<T> Finally<T>(this TryWrapper<T> wrapper, Func<T> response)
        {
            wrapper.Result = response.Invoke();

            return wrapper;
        }

        public static TryWrapper<T> Finally<T>(this TryWrapper<T> wrapper, Action response)
        {
            response.Invoke();

            return wrapper;
        }

现在这确实实现了 OP 要求的内联语法,但我敢说它的效率较低,因为您可以直接在标准的 try-catch 中处理异常。能够在 Try 之前直接指定 return 仍然有点酷,尽管如果默认为 null 则有风险。

【讨论】:

    【解决方案3】:
    _.Try(() => __YourStatementHere__ );
    

    像这样使用一个小助手类:

    /// <summary>
    /// Other utility functions.
    /// </summary>
    public static class _ {
    
        /// <summary>
        /// Tries to execute the given action. If it fails, nothing happens.
        /// </summary>
        public static void Try(Action action) {
            try {
                action();
            }
            catch {
            }
        }
    
    }
    

    我知道,这个解决方案也不是最优的,但是到目前为止我能找到的最简洁的一个。

    【讨论】:

      【解决方案4】:

      首先尽量避免异常。仅仅因为字符串来自您无法控制的来源,并不意味着您无法验证它。

      如果无法避免,则应捕获您期望的特定异常,并将该逻辑封装在方法中。不要捕获所有异常。

      例如:

      public static CultureInfo TryGetCultureByName(string name)
      {
         try
         {
           return new CultureInfo(name);
         }
         catch(CultureNotFoundException)//Only catching CultureNotFoundException
         {
           return null;
         }
      }
      

      这样,如果您以后发现处理此特定错误的更好方法,您可以轻松替换它。

      例如,您可以创建一个Dictionary&lt;string, CultureInfo&gt;,从CultureInfo.GetCultures() 填充它,然后使用TryGetValue 来查找文化而不抛出异常。

      【讨论】:

        【解决方案5】:

        您是否尝试过完全摆脱 try/catch 语句?

        string clientLanguage = null;
        var userLanguages = filterContext.HttpContext.Request.UserLanguages;
        if (userLanguages != null && userLanguages.Length > 0)
        {
            var culture = CultureInfo
                .GetCultures(CultureTypes.AllCultures)
                .FirstOrDefault(
                    x => string.Equals(
                        x.Name, 
                        userLanguages[0].Name, 
                        StringComparison.OrdinalIgnoreCase
                    )
                );
            if (culture != null)
            {
                clientLanguage = culture.TwoLetterISOLanguageName;
            }
        }
        

        仅使用 try/catch 来处理您无法控制的异常。顾名思义,应该使用异常来处理异常情况。

        在这种情况下,您正在执行标准解析,因此最好进行防御性编程,而不是尝试、抛出、捕捉……

        【讨论】:

        • 我认为这个异常几乎是失控的,不是吗? (编辑了我的帖子)
        • 当然不是失控。看我的代码。我正在控制它。 OP 试图将 TwoLetterISOLanguageName 直接传递给 CultureInfo 构造函数。显然,如果客户端传递了一些无效的语言,这可能会引发异常。在我的示例中,我试图从无法抛出的所有可能值的列表中找到相应的 CultureInfo。
        • 嗯,FirstOrDefault 返回一个 CultureInfo,但您将它分配给一个字符串。不过喜欢这个主意。
        • @JoachimIsaksson,好点子,让我解决这个问题。答案已更新。
        • 如果这个太慢可以转成Dictionary&lt;string, CultureInfo&gt;
        【解决方案6】:

        好吧,撇开关于预先检查的(好的)建议不谈,有几种相当普通/平淡/明显的方法可以做到这一点:

        首先,您可以将其包装在一个函数中。我认为这对你来说不够笼统。

        或者,您可以折叠 catch 分支:

        try
        {
            clientLanguage = (filterContext.HttpContext.Request.UserLanguages != null) ? new CultureInfo(filterContext.HttpContext.Request.UserLanguages[0]).TwoLetterISOLanguageName : null; // Exception sometimes without `try`
        } catch (Exception) { }
        

        或者,您可以将整个内容折叠成一行:

        try { clientLanguage = (filterContext.HttpContext.Request.UserLanguages != null) ? new CultureInfo(filterContext.HttpContext.Request.UserLanguages[0]).TwoLetterISOLanguageName : null; } catch (Exception) { }
        

        不优雅,但简单,而且有效。

        【讨论】:

          【解决方案7】:

          首先,最好首先弄清楚如何避免异常。首先集中精力。抛出这个异常是有原因的,如果你能确定它是什么,那就不要这样做。

          要真正回答您的问题:没有开箱即用的“吃掉此表达式中的所有异常”机制,但构建自己的机制很简单:

          static T EatExceptions(Func<T> func)
          {
            try { return func(); } catch { }
            return default(T);
          }
          ...
          clientLanguage = (filterContext.HttpContext.Request.UserLanguages != null) ? 
            EatExceptions(() => new CultureInfo(filterContext.HttpContext.Request.UserLanguages[0]).TwoLetterISOLanguageName) :
            null; }
          

          如果有人试图在我正在审查的代码中进行这样的恶作剧,那么我会......好吧,我们只是说更改不会被签入。像这样吃异常在 99% 的情况下都是一个非常糟糕的主意。再说一遍:找出你做错了什么并停止这样做。不要做错事,然后处理失败。

          【讨论】:

          • 我完全同意您应该始终避免异常,因为它的性能不是很好。我有一个案例,我希望我的应用程序尝试做某事,如果失败了,这并不重要,因为这是多余的。
          • @DanAtkinson:问题不在于异常很慢。与加法相比,异常很慢,但那是因为加法非常快。您可以在一秒钟内抛出数千个异常,而不必担心性能影响。问题是异常应该是异常的。 throw 是一个非本地 goto;很难想象比这更容易混淆和破坏性的控制流。因此,它应该仅限于罕见的、经过仔细分析的案例。
          • 我当然同意你所说的。这个答案可能是一个更广泛的问题的膏药,“掩盖”任何处理它的尝试都是违反直觉的。 :-)
          【解决方案8】:

          您所做的是正确的方法。你说,为什么你不能摆脱异常(我认为是这种情况)。所以你必须处理它。唉,C# 没有 try-catch 作为表达式(不确定它是如何工作的 - catch“子句”需要返回一个值)。

          或者,您可以构建一个小辅助函数,该函数接受Func&lt;T&gt;,调用它并将值传递给调用者。如果发生异常,则返回(例如)default(T)。这样可以消除很多混乱并且可以重复使用。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-05-22
            • 1970-01-01
            • 1970-01-01
            • 2012-03-03
            • 2011-10-26
            • 2015-08-05
            • 2011-06-04
            相关资源
            最近更新 更多