【问题标题】:C# generics code-bloat - should I be worried?C# 泛型代码膨胀——我应该担心吗?
【发布时间】:2017-09-23 13:47:06
【问题描述】:

我想问一些关于泛型的问题。

我试图保持代码简单,因此我将创建一个类来处理游戏存档文件的加载/保存。由于游戏的每个部分都有不同的要求,因此我希望尽可能方便地访问:

public void Load<T>(string path, out T obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (FileStream file = File.Open(Application.persistentDataPath + path, FileMode.Open))
    {
        obj = (T)bf.Deserialize(file);
    }
}

现在我可以用一个简单的方法来调用它

TurnData x; s.Load("test.txt", out x);

另一种方法是让 Load 函数返回对象,然后将其转换为 TurnData 类型。

TurnData x = (TurnData)s.Load("test.txt");

我对 C# 了解不多。例如,如果打开文件时出错,我假设using(...) { ... } 中的代码不会被执行?如果有人能证实这一点,那就太好了。我看到的示例代码没有任何错误处理,这对我来说似乎很奇怪,所以我添加了 using?

所以在这个函数返回对象而不是使用 out 参数的次要版本中,需要更复杂的代码来进行错误检查并可能返回 null?好像不太好。

所以真正的问题是......我可以使用我在这里拥有的下一个版本,还是因为使用泛型而我应该担心?

【问题讨论】:

  • using 块只是try{} finally{ //will dispose disposable object }的简化版
  • 基本上是调用对象的Dispose() 方法,即使块内的代码抛出异常。
  • 我不明白你为什么要使用泛型编写代码。为什么 Foo foo; Load&lt;Foo&gt;(path, out foo); 在任何方面都比 Foo foo = (Foo)Load(path); 好?后者对我来说似乎更清楚,更短更简单。你这样做的建议有很大的价值,你应该考虑这样做。无论哪种方式,您都必须编写错误处理。
  • @EricLippert,这不是out 变量的问题吗?我真的不明白为什么给出输出会使它更具可读性?

标签: c# generics


【解决方案1】:

reference types 没有通用代码膨胀 - 代码被重用。但是,对于值类型,CLR 将为每种类型生成一个单独的方法。看 .NET Generics and Code Bloat.

【讨论】:

  • 我知道我已经读过了,这就是为什么我明确指出 Mono 存在,但不一定适用
【解决方案2】:

using 语句与错误处理无关。使用File.Open 方法,您可以期望得到您会发现here 的异常。通过将 using 语句包装在 try/cath 构造中,您可以避免程序因任何此类异常而突然停止,如下所示:

public T Load<T>(string path)
{
    T obj = default(T);
    var bf = new BinaryFormatter();
    try
    {
        using (var file = File.Open(Application.persistentDataPath + path, FileMode.Open))
        {
            obj = (T)bf.Deserialize(file);
        }
    }
    catch(Exception exception)
    {
        // Log the exception
    }
    return obj;

}

基本上你试图打开路径中指定的文件。如果失败,您只需记录失败并从函数返回null

关于using语句,它提供了

确保正确使用 IDisposable 的便捷语法 对象。

因为您可以更彻底地阅读here

作为关于您的方法签名的旁注,我会做一些 cmets。考虑下面的方法体并找出与我们上面的不同之处。

public T Load<T>(string path, IFormatter formatter)
{
    if(path ==null) throw new ArgumentNullException(nameof(path));
    if(formatter == null) throw new ArgumentNullException(nameof(formatter));

    T obj = default(T);
    try
    {
        using (var file = File.Open(path, FileMode.Open))
        {
            obj = (T)formatter.Deserialize(file);
        }
    }
    catch(Exception exception)
    {
        // Log the exception
    }
    return obj;   
}

var path = Path.Combine(Application.persistentDataPath, "test.txt");
var binaryFormatter = new BinaryFormatter();
var x = s.Load(path, binaryFormatter);

进行上述更改可以使您的方法更容易通过单元测试进行测试并且更可靠,因为您在方法的肉和土豆之前进行了一些前提条件检查。如果你传递了一个空的path 会发生什么?如果您通过了一个空格式化程序会发生什么?等等……

【讨论】:

  • 此代码无法编译:CS0403 无法将 null 转换为类型参数“T”,因为它可能是不可为空的值类型。考虑改用“default(T)”。
  • @PavelTupitsyn 你说得非常正确!我刚刚纠正了它。谢谢
  • @StuartLC 非常感谢您的评论。帕维尔也指出了这一点:)
  • 上面的示例代码违反了几个 C# 最佳实践:不要抛出 NullReferenceExceptions。有一种专门针对这种情况的异常类型:ArgumentNullException 请参阅:stackoverflow.com/questions/22453650。另外,不要捕获一般异常,请参阅:stackoverflow.com/questions/1742940
  • @DaveM 感谢您的评论。对于第一点,我完全同意你的看法。我的错......事实上,我们从未为空参数值抛出 NullReferenceExceptions。这就是我刚刚纠正它的原因。但是关于第二点,我有不同的看法。特别是如果您不希望您的程序意外崩溃,则无论何时可能抛出可能的异常,您都必须捕获所有异常并采取适当的行动。我从这种情况下排除了异常,例如表明方法使用不当的空参数异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多