数组索引返回null而不是越界

13

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

例如,我有以下类:

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 < Ids.Length”)


1
编写行为类似的代码很容易,但你应该质疑自己的动机。所有理智的数据结构都提供了方法来确定索引访问是否合法,而不需要使用“try/catch”。为什么要自己编写“越界处理”而不使用现有的工具呢? - Jon
1
在输入第100个if语句检查是否在范围内后,我已经厌倦了,并想问是否可能有类似的方法。我知道这不是标准行为,但我认为可能有一种方法可以做到这一点。也许是静态扩展或其他什么,我不知道。 - Rand Random
1
你已经厌倦了这个并决定检查结果是否为null?这样有所改善吗? - Jon
1
不是很好,因为在我的应用程序中,我不关心它是null还是int。我只想将数组项传递给可为空类型的变量,之前答案中有一个足够满足我的扩展方法,但它被删除了。 :( - Rand Random
@RandRandom,我删除了我的答案,因为不幸的是你不能将T用作Nullable<T>参数。不过我正在寻找替代方案... - James
显示剩余2条评论
8个回答

10
如果你只对已经确定为非空类型数据感兴趣,例如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];
}

3

好的,我们采用全新的方法。由于您有多种可能的类型并且想要一个“万能牌”方法,您可以将这些值存储为键/值集合在您的类中,然后此类方法就变得可能了。

首先,内部存储这些值:

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" });

最后是Joker方法:

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);
  //...
}

1

有些人可能会抱怨这可能是一种额外负担,但如果你使用 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

1

在这里你真的没有太多可以做的,除了:

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

或者,使用 ? 运算符:
return (i >= array.Length) ? null : array[i];

但我对此的问题是,我需要一遍又一遍地询问这个 - 难道没有一种方法可以“扩展”索引行为,使其始终按照这种方式工作吗? - Rand Random

1
  1. 从我所了解的来看,您正在实现一个数组类型的属性,而不是索引器。
  2. 这有点像是伪造超出范围的索引,如果你在代码中关心越界问题,那么仍然会更好。说到底,当范围被违反时,没有人阻止你分配一个默认值(在您的情况下为NULL)。

如果您需要上述情况的快捷方式,我建议在您的类中使用以下方法:

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

}

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

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

0

这里似乎需要使用类索引。以下是针对您的TestClass示例的直接答案。

您还可以派生自己的自定义集合类,严格用于存储内部int[]类型的Ids,并覆盖所有适当的访问调用,例如Add、Remove等(并像这样索引集合以使其更易于使用)。然后,您可以在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;
    }

0

请尝试:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接