如何从未初始化的接口参数中优雅地保护方法

时间:2018-05-18 16:35:38

标签: c#

给定接口类型的方法参数,如果消费者可以提供引用类型或未知值类型,即开发人员不知道的结构类型,如何保护方法免受未初始化参数的影响在实施时?我不想检查传入的属性的特定属性值。我已经对这个主题进行了大量的搜索,并且找不到我正在寻找的信息。

E.g。

public interface IFoo {}

// A type I don't know about
public struct FooStruct : IFoo {}

public class FooClass : IFoo {}

......在其他一些课程中

public void DoSomething(IFoo foo)
{
     // Check if foo is null or default of some value type
     // Just like foo == null, this doesn't work for a value type 
     if (foo == default(IFoo)) throw new ArgumentNullException(nameof(foo));

}

4 个答案:

答案 0 :(得分:3)

如果方法因为它们实际上不会中断,请不要担心初始值。与null相比就足够了,因为您尝试使用指定的参数名称抛出先前的异常,而不是可怕的和非特定的NullReferenceException; throw的全部目的是添加更具体的细节,以便调用实现可以轻松修复他们做错的事情(或者至少报告更明智且更有意义的错误)。一个很好的例子就是Linq方法Enumerable.Take由于count而无法投掷; anything less than or equal to 0 simply results in an empty collection

话虽如此,如果FooStruct的某些内容无效,应该为该无效性抛出一个特定的例外,有点误导ArgumentNullException 。例如,考虑一下,虽然default(int)0,但0完全有效,无法传递到某些方法,如果不是,我们期望ArgumentOutOfRange (希望提到有效范围)而不是ArgumentNull我们知道的值是(并且永远不会)null

答案 1 :(得分:1)

当传递给接受此结构实现的接口的方法时,FooStruct将被装箱,因此您可以将其与null进行比较。也许你可能想读一下拳击,也许这篇文章也会有所帮助:Gotcha-When-Csharp-Structures-Implement-Interfaces

答案 2 :(得分:1)

对于不是structs的{​​{1}},您可以将结构与Nullable<T>(下面的Activation.CreateInstance)进行比较。

new MyStruct()

以上代码输出以下内容:

void Main()
{
    FooStruct fooStruct = new FooStruct() { FooProp = 1234 };
    FooStruct fooStructDefault = default(FooStruct);
    FooClass fooClass = new FooClass();
    FooClass fooClassDefault = default(FooClass);

    IsDefaultIFoo(fooStructDefault).Dump();
    IsDefaultIFoo(fooStruct).Dump();
    IsDefaultIFoo(fooClassDefault).Dump();
    IsDefaultIFoo(fooClass).Dump();

}

public bool IsDefaultIFoo(IFoo foo)
{
    if(foo != null && foo.GetType().IsValueType)
    {
        return foo.Equals(Activator.CreateInstance(foo.GetType()));
    }
    else
    {
        return foo == default(IFoo);    
    }   
}

public interface IFoo { }

public struct FooStruct : IFoo
{ 
    public int FooProp { get; set;}
}

public class FooClass : IFoo { }

答案 3 :(得分:0)

根据contracts来推断您的代码通常很有帮助。

每个方法都有一些前提条件 - 被调用者应该满足的东西,以及后置条件 - 方法保证对有效输入做的事情。

例如,方法divide y by x x显然应该是非零蚂蚁,这是一个合理的前提条件。对于这种情况,后置条件是结果小于或等于提供的y

只能在有效输入上保证有效行为,因此没有理由抛出默认的struct值,因为它是默认的 - 而是做一些特定而合理的断言。

ArgumentNullException之所以如此广泛,只是因为通常以某种方式使用参数,所以验证它们不是null是有意义的,否则你的方法会失败。