【问题标题】:Array index return null instead of out of bound数组索引返回 null 而不是越界
【发布时间】:2013-11-17 12:24:12
【问题描述】:

我目前正在尝试在我的类定义中实现“索引”属性。

例如我有以下课程:

public class TestClass
{
   private int[] ids = null;

   public string Name { get; set; }
   public string Description { get; set; }
   public int[] Ids { 
      get
      {
         //Do some magic and return an array of ints 
         //(count = 5 - in this example in real its not fixed)
         return _ids;
      }
   }
}

现在我喜欢这样使用这个类:

private void DoSomething()
{
   var testClass = GetSomeTestClass();
   //work with the ids

   for (int i = 0; i < 10; i++) //I know I could say i < Ids.Length, its just an example
   {
      int? id = testClass.Ids[i];
      //this will result, in a out of bound exception when i reaches 5 but I wish for it to return a null like a "safe" index call ?!?
   }
}

那么是否有一个安全的索引调用会导致 null,而无需我在 try catch 中一次又一次地包装它。

另一件事我不希望使用类索引,因为我需要几个像这样工作的属性,具有不同的类型(int、string、bool、自定义类等)。

(for 只是一个简单的例子,我知道在这种情况下我可以说“i

【问题讨论】:

  • 编写这样的代码很容易,但你应该质疑你的动机。 所有健全的数据结构为您提供了确定索引访问是否合法的方法,而无需诉诸try/catch。为什么要推出自己的“越界处理”而不是使用现有设施?
  • 在输入第 100 个 if 语句检查它是否在边界后,我厌倦了它,想问这样的事情是否可能。我知道这不是标准行为,但我想也许有办法做到这一点。可能是静态扩展什么的,我不知道。
  • 您厌倦了它并决定检查结果是否为null?这在某种程度上是一种改进吗?
  • 不是真的,因为在我的应用程序中,我不在乎它是 null 还是 int。我只想将一个数组项传递给一个可为空的类型变量,几秒钟前的答案中有一个扩展方法对我来说已经足够了,但是它被删除了。 :(
  • @RandRandom 我删除了我的答案,因为不幸的是你不能使用 T 作为 Nullable&lt;T&gt; 参数。我正在研究另一种方法......

标签: c# .net properties indexing


【解决方案1】:

如果您只对已经不可为空的类型数据感兴趣,例如struct 你可以使用简单的扩展方法,例如

public static class ArrayExt
{
    public static Nullable<T> GetValueOrNull(this T[] array, int index) where T: struct
    {
        return array.Length < index ? new Nullable<T>(array[index]) : null;
    }
}

这将允许您简单地调用

int? id = testClass.Ids.GetValueOrNull(i);

但是,鉴于您需要支持任意数量的类型,我的建议是在数组周围实现一个包装器并控制您访问数据的方式,例如

public class SafeArray<T>
{
    private T[] items;

    public SafeArray(int capacity)
    {
        items = new T[capacity];
    }

    public object this[int index]
    {
        get
        {
            return index < items.Length ? (object)items[index] : null;
        }
        set
        {
            items[index] = (T)value;
        }
    }
}

public class TestClass
{
    public TestClass()
    {
        Ids = new SafeArray<int>(5);
        Instances = new SafeArray<MyClass>(5);
    }
    ...
    public SafeArray<int> Ids { get; private set; }

    public SafeArray<MyClass> Instances { get; private set; }
}

这种方法的关键是使用object 作为返回类型。这允许您将数据转换为接收端的预期类型(或者如果使用值类型,则将其装箱/取消装箱),例如

for (int i = 0; i < 10; i++)
{
    // we need an explicit cast to un-box value types
    var id = (int?)testClass.Ids[i];
    // any class is already of type object so we don't need a cast
    // however, if we want to cast to original type we can use explicit variable declarations e.g.
    MyClass instance = testClass.Instances[i];
}

【讨论】:

    【解决方案2】:

    好的,全新的方法。由于您有几种可能的类型并且想要一个“joker”方法,因此您可以将值作为键/值集合存储在您的类中,然后这种方法成为可能。

    首先,在内部存储值:

    public class TestClass
    {
         private Dictionary<Type, Array> _values = new Dictionary<Type, Array>();
    }
    

    现在用实际数据填充该集合:

    _values.Add(typeof(int?), new int[] { 1, 2, 3 });
    _values.Add(typeof(string), new string[] { "a", "b", "c", "d", "e" });
    

    最后是小丑方法:

    public T Get<T>(int index)
    {
        Type type = typeof(T);
        Array array;
        if (_values.TryGetValue(type, out array))
        {
            if (index >= 0 && index < array.Length)
            {
                return (T)array.GetValue(index);
            }
        }
        return default(T);
    }
    

    用法:

    for (int i = 0; i < 10; i++)
    {
      int? id = testClass.Get<int?>(i);
      string name = testClass.Get<string>(i);
      //...
    }
    

    【讨论】:

      【解决方案3】:

      除了这里你真的没有什么可以做的:

      if (i >= array.Length) return null;
      else return array[i];
      

      或者,使用? 运算符:

      return (i >= array.Length) ? null : array[i];
      

      【讨论】:

      • 但是我有这个问题,我需要一次又一次地要求这个 - 没有办法“扩展”它总是这样工作的索引行为吗?
      【解决方案4】:

      你可以使用方法而不是属性:

      public int? Ids(int i) {
          if (i >= 0 && i < _ids.length)
          {
              return _ids[i];
          }
          return null;
      }
      

      【讨论】:

        【解决方案5】:
        1. 根据我的阅读,我看到您实现了数组类型的属性,但不是索引器
        2. 这是一种伪造索引超出范围情况的举动,如果您在代码中考虑超出范围,情况会好得多。归根结底,当违反范围时,没有人阻止您分配默认值(在您的情况下为 NULL)

        如果您需要针对上述情况的快捷方式,我会在您的班级中采用以下方法:

         public int? ReadAtOrNull(int index)
        {
          return index < ids.Lenght && index > 0 ? (int?)ids[index] : null;
        
        }
        

        【讨论】:

          【解决方案6】:

          人们可能会开始抱怨这可能是开销,但如果您使用 SkipFirstOrDefault 会怎样?

          for (int i = 0; i < 10; i++) //I know I could say i < Ids.Length, its just an example
          {
             int? id = testClass.Ids.Skip(i).FirstOrDefault();
          
          }
          

          请注意,在这种情况下,您可能需要将数组声明为 int?[],否则默认值为 0 而不是 null

          【讨论】:

            【解决方案7】:

            请尝试:

            for (int i = 0; i <  Ids.Length; i++) 
               {
                   if (!String.IsNullOrEmpty(testClass.Ids[i].Tostring()) 
                  int? id = testClass.Ids[i];
               }
            

            【讨论】:

              【解决方案8】:

              这里的想法似乎是使用类索引。这是您的TestClass 示例的直接答案。

              您还可以严格为 Id 派生您自己的自定义集合类,该类在内部存储 int[] 并覆盖所有适当的访问调用,即添加、删除等。(并像这样对集合进行索引以使其更容易使用)。然后,您可以在 TestClass 中拥有一个名为 Ids 的属性,其行为类似于示例。

              我知道这个问题已经 3 个月了,但我希望这仍然有帮助。

              public class TestClass {
              
                  private int[] ids = new int[] { 1, 2, 3, 4, 5 };
                  public string Name { get; set; }
                  public string Description { get; set; }
              
                  public int? this[int index] {
                      get {
                          if (index < 0 || index > ids.Length - 1)
                              return null;
              
                          return ids[index];
                      }
                      set {
                          if (value == null)
                              throw new ArgumentNullException(
                                  "this[index]", 
                                  "Ids are not nullable"
                              );
              
                          ids[index] = (int)value;
                      }
                  }
              }
              

              用法:

                  private void DoSomething() {
                      TestClass testClass = new TestClass();
              
                      for (int i = 0; i < 10; i++) {
                          int? id = testClass[i];
                      }
              
                      // You can assign to the Ids as well
                      testClass[0] = 6;
                  }
              

              【讨论】:

                猜你喜欢
                • 2013-07-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-14
                相关资源
                最近更新 更多