我应该如何重构一连串的尝试和捕获包裹的投机性投机操作

时间:2009-09-27 19:42:17

标签: c# .net refactoring xsd try-catch

我有一些C#代码,它们使用.NET框架中的Xml.Schema类来处理XML模式。各种简单类型限制在框架中被抽象为从Xml.Schema.XmlSchemaFacet派生的一大堆类。除非有一些我错过的东西,否则知道给定方面的哪个派生方面类型的唯一方法是推测性地将其转换为其中一个,在失败的情况下捕获结果的InvalidCastOperation。这样做会给我带来一个非常难看的功能:

private void NavigateFacet(XmlSchemaFacet facet)
{
    try
    {
        handler.Length((XmlSchemaLengthFacet)facet);
    }
    catch(InvalidCastException)
    {
        try
        {
            handler.MinLength((XmlSchemaMinLengthFacet)facet);
        }
        catch(InvalidCastException)
        {
            try
            {
                handler.MaxLength((XmlSchemaMaxLengthFacet)facet);
            }
            catch(InvalidCastException)
            {
                ...
            }
        }
    }
}

我认为必须有更优雅的方法来做到这一点;要么使用我从.NET框架中遗漏的一些属性,要么使用一些聪明的OO技巧。谁能开导我?

6 个答案:

答案 0 :(得分:7)

因为我更喜欢将数据调试到调试代码,所以我会这样做,特别是如果代码必须处理所有XmlSchemaFacet子类:

Dictionary<Type, Action<XmlSchemaFacet>> HandlerMap = 
   new Dictionary<Type, Action<XmlSchemaFacet>>
{
   {typeof(XmlSchemaLengthFacet), handler.Length},
   {typeof(XmlSchemaMinLengthFacet), handler.MinLength},
   {typeof(XmlSchemaMaxLengthFacet), handler.MaxLength}
};

HandlerMap[facet.GetType()](facet);

如果KeyNotFoundException不属于已知类型,则会抛出facet。请注意,所有处理程序方法都必须从XmlSchemaFacet转换其参数,因此您可能无法保存代码总数,但您肯定会节省代码中的路径数。

还有一点(假设地图是预先构建的)将字典映射到带有字典的方法将比遍历线性类型列表更快,这实质上就是使用一堆if积木让你。

答案 1 :(得分:6)

您可以尝试使用as关键字。其他一些人建议使用is关键字。我发现this可以很好地解释为什么as更好。

一些示例代码:

private void NavigateFacet(XmlSchemaFacet facet)
{
  XmlSchemaLengthFacet lengthFacet = facet as XmlSchemaLengthFacet;
  if (lengthFacet != null)
  {
    handler.Length(lengthFacet);
  }
  else
  {
    // Re-try with XmlSchemaMinLengthFacet, etc.
  }
}

答案 2 :(得分:4)

private void NavigateFacet(XmlSchemaFacet facet)
{
    if (facet is XmlSchemaLengthFacet)
    {
        handler.Length((XmlSchemaLengthFacet)facet);
    }
    else if (facet is XmlSchemaMinLengthFacet)
    {
        handler.MinLength((XmlSchemaMinLengthFacet)facet);
    }
    else if (facet is XmlSchemaMaxLengthFacet)
    {
        handler.MinLength((XmlSchemaMaxLengthFacet)facet);
    }

    // etc.
}

更新:我决定对此处讨论的不同方法进行基准测试(isas)。这是我使用的代码:

object c1 = new Class1();
int trials = 10000000;
Class1 tester;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < trials; i++)
{
    if (c1 is Class1)
    {
        tester = (Class1)c1;
    }
}
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~104 ms 
watch.Reset();
watch.Start();
for (int i = 0; i < trials; i++)
{
    tester = c1 as Class1;
    if (tester != null)
    {
        // 
    }
}
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~86 ms
watch.Reset();
watch.Start();
for (int i = 0; i < trials; i++)
{
    if (c1 is Class1)
    {
        // 
    }
}
watch.Stop();     
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~74 ms
watch.Reset();
watch.Start();
for (int i = 0; i < trials; i++)
{
    //
}
watch.Stop();     
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~50 ms

正如预期的那样,使用as关键字然后检查null 比使用is关键字和转换更快(36 ms与54 ms相比,减去成本循环本身)。

但是,使用is关键字,然后投射更快(24毫秒),这意味着使用一系列{找到正确的类型{1}}检查,然后只在识别出正确的类型时才进行一次 实际上更快(取决于在确定正确的类型之前必须在此方法中完成的不同类型检查的数量) )。

然而,更深层的是,此测试中的试验次数为10 这意味着您使用哪种方法确实没有多大区别。使用is和强制转换需要0.0000054毫秒,而使用is并检查null需要0.0000036毫秒(在我的古老笔记本上)。

答案 3 :(得分:3)

您可以尝试使用as关键字 - 如果投射失败,您会获得null而不是例外。

private void NavigateFacet(XmlSchemaFacet facet)
{
    var length = facet as XmlSchemaLengthFacet;
    if (length != null)
    {
        handler.Length(length);
        return;
    }

    var minlength = facet as XmlSchemaMinLengthFacet;
    if (minlength != null)
    {
        handler.MinLength(minlength);
        return;
    }

    var maxlength = facet as XmlSchemaMaxLengthFacet;
    if (maxlength != null)
    {
        handler.MaxLength(maxlength);
        return;
    }
    ...
}

如果您可以控制类,我建议使用访问者模式的变体(又名Double Despatch)来更清晰地恢复类型信息,但是由于您没有,这是一种相对简单的方法

更新:使用该变量存储as强制转换的结果,无需经过两次类型检查逻辑。

更新2 :当C#4可用时,您将能够使用dynamic为您执行调度:

public class HandlerDemo 
{
    public void Handle(XmlSchemaLengthFacet facet) { ... }
    public void Handle(XmlSchemaMinLengthFacet facet) { ... }
    public void Handle(XmlSchemaMaxLengthFacet facet) { ... }
    ...
}

private void NavigateFacet(XmlSchemaFacet facet)
{
    dynamic handler = new HandlerDemo();
    handler.Handle(facet);
}

这将起作用,因为动态对象上的方法分派使用与普通方法重写相同的规则,但在运行时而不是编译时进行评估。

在幕后,动态语言运行时(DLR)将采用与此(和其他答案)中显示的代码相同的技巧,但增加了性能缓存。

答案 4 :(得分:1)

这是一种没有任何尝试捕获的正确方法。

if (facet is XmlSchemaLengthFacet)
{
    handler.Length((XmlSchemaLengthFacet)facet); 
} 
else if (facet is XmlSchemaMinLengthFacet)
{
    handler.MinLength((XmlSchemaMinLengthFacet)facet); 
} 
else if (facet is XmlSchemaMaxLengthFacet)
{
    handler.MaxLength((XmlSchemaMaxLengthFacet)facet); 
} 
else
{
    //Handle Error
}

答案 5 :(得分:0)

  • 使用“是”来确定某个对象是否属于给定类型

  • 使用“as”进行类型转换,它比普通的转换更快,并且不会抛出异常,而是在错误时重新生成null

你可以这样做:

private void NavigateFacet(XmlSchemaFacet facet)
{
  if (facet is XmlSchemaLengthFacet)
  {
        handler.Length(facet as XmlSchemaLengthFacet);
  }
  else if (facet is XmlSchemaMinLengthFacet)
  {
        handler.MinLength(facet as XmlSchemaMinLengthFacet);
  }
  else if (facet is XmlSchemaMaxLengthFacet)
  {
       handler.MaxLength(facet as XmlSchemaMaxLengthFacet);
  }
}