.NET中是否有一个通用的(类型安全的)BitArray?

4
在.NET中是否有通用的BitArray?我只找到了非泛型的。
是否可以有一个通用的BitArray?(即是否合理?)

编辑:

也许我应该说类型安全而不是通用的。

基本上,当您将类型枚举为object时,它不应该是intbool吗?或者在另一个成员枚举器中提供其中之一?


示例:

foreach (bool bit in myBitArray)
{

}

编辑:

我刚刚检查了BitArray类的枚举器,但除了.Current属性之外,所有内容都返回一个object

public virtual object Current

3
你需要那个有什么用?我的意思是,BitArray只是一个位数组... 它怎么可能是通用的呢? - Thomas Levesque
2
你会用它做什么?有多种类型的比特吗?:O - Robert Harvey
我之前的问题表述不正确,已经更新。 - Joan Venge
在我的位数组中,那个需要成为 bool 位。 - Richard Anthony Freeman-Hein
正确的,BA方法似乎返回布尔值。 - Joan Venge
1
笑话:我猜你永远不知道什么时候需要“是”,“否”和“文件未找到”。 - BlackTigerX
5个回答

9

BitArray是来自.NET 1.x时代的专门集合类。只要使用ba.Set(int, bool)和索引器属性,它就非常类型安全。

不类型安全的是枚举,BitArray实现了IEnumerable但没有实现IEnumerable<bool>。所以Joan是对的,使用foreach()需要将对象强制转换为bool。

但这是一个真正的问题吗?BitArray中的元素是布尔值,并且只有与它们的位置结合使用时才有意义。请注意,BitArray没有Add()方法,只有Set(i, true)方法。

因此,简单的答案是:不要使用foreach()或任何其他基于IEnumerable的方法。它只会生成一系列几乎无用的true/false值。

在下面的代码片段中,BitArray是完全类型安全和高效的:

BitArray isEven = ...;
for(int i = 0; i < isEven.Count; i++) 
{
   isEven.Set(i, i % 2 == 0);
}

枚举位集是发现有多少位被设置的唯一方法,我经常觉得这很有用。如果实现了一个正确的 IEnumerable<bool>,将会使 Linq 查询更加优雅,并且消除不必要的装箱/拆箱。 - ChrisWue

8

没有。

如果有一个通用的位数组,我甚至不确定其中哪一部分会是通用的。

很容易创建一个扩展方法,以取BitArray并使用for循环返回一个bool[]List<bool>。由于您将使用BitArray的索引器,因此for循环不涉及装箱,也可以枚举bool[]List<bool>而不涉及装箱。

示例扩展方法:

static List<bool> ToList( this BitArray ba ) {
    List<bool> l = new List<bool>(ba.Count);
    for ( int i = 0 ; i < ba.Count ; i++ ) {
        l.Add( ba[ i ] );
    }
    return l;
}

从一个快速的基准测试中,我发现 foreach (bool b in myBitArray.ToList()) 所需的时间是 foreach (bool b in myBitArray) 的 75% 到 85%。这是因为每次都会创建一个新列表。如果只创建一次列表并多次迭代,那么所需的时间为 foreach (bool b in myBitArray) 的 20% 到 25%。只有当你需要多次迭代布尔值,并且知道在调用 myBitArray.ToList() 后它们不会改变时,才能利用此功能。 foreach (bool b in Enumerable.Cast<bool(myBitArray)) 所需的时间是 foreach (bool b in myBitArray) 的 150%。 再次编辑: 我认为既然这是一个游戏,尽管这意味着编写自己的BitArray,但你应该尽可能地使迭代非常精简,避免装箱/拆箱。你可以通过使用Reflector来节省时间并研究复制大部分BitArray代码,因为该类是密封的(无法继承和添加功能),以防万一有比特位操作可以学习。 编辑: 取消了从Reflector复制代码的建议。一些东西,比如迭代器和闭包,会生成一些奇怪的代码,你不想直接复制它们。

这就是基准测试的作用。创建一个小型项目,对于大型BitArray执行每个建议数千次。确保在计时之前先调用每个建议,以避免JIT编译干扰您的时间。 - Joel B Fant
谢谢,这正是我想要创建一个新列表进行迭代的想法。 - Joan Venge
是的,这可能是我最终要做的,就像你第二次编辑中提到的那样。 - Joan Venge
为什么要将其转换为列表,而不是为BitArray编写一个IEnumerable<bool>迭代器呢?这会消耗大量内存(每个项目需要1字节而不是1位),并且可能会更慢。 - Alexey Romanov
@alexey_r:我对那个做了基准测试,甚至比Enumerable.Cast<T>还要慢。 - Joel B Fant
显示剩余6条评论

7

您可以在不将其装箱或转换为List<bool>的情况下迭代BitArray

public static IEnumerable<bool> GetTypeSafeEnumerator(this BitArray ba) {
    for (int i = 0; i < ba.Length; i++)
        yield return ba[i];
}

这应该比转换为列表要快,肯定需要更少的内存。
当然,它仍然比普通的for循环慢,如果你真的需要性能,你应该使用。
for (int i = 0; i < ba.Length; i++) {
    bool b = ba[i];
    ...
}

使用MiniBench进行基准测试:

public static class Class1 {
    private const int N = 10000;
    private const int M = 100;

    public static void Main() {
        var bitArray = new BitArray(N);

        var results1 = new TestSuite<BitArray, int>(
            "Different looping methods")
            .Plus(PlainFor, "Plain for loop")
            .Plus(ForEachBool, "foreach(bool bit in bitArray)")
            .Plus(CastBool, "foreach(bool bit in bitArray.Cast<bool>)")
            .Plus(TypeSafeEnumerator, "foreach(bool bit in bitArray.GetTypeSafeEnumerator())")
            .Plus(UseToList, "foreach(bool bit in bitArray.ToList())")
            .RunTests(bitArray, 0);

        results1.Display(ResultColumns.All, results1.FindBest());

        var results2 = new TestSuite<BitArray, int>(
            "Avoiding repeated conversions")
            .Plus(PlainFor1, "Plain for loop")
            .Plus(CastBool1, "foreach(bool bit in bitArray.Cast<bool>)")
            .Plus(TypeSafeEnumerator1, "foreach(bool bit in bitArray.GetTypeSafeEnumerator())")
            .Plus(UseToList1, "foreach(bool bit in bitArray.ToList())")
            .RunTests(bitArray, 0);

        results2.Display(ResultColumns.All, results2.FindBest());
    }

    private static int PlainFor1(BitArray arg) {
        int j = 0;
        for (int k = 0; k < M; k++) {
            for (int i = 0; i < arg.Length; i++) {
                j += arg[i] ? 1 : 0;
            }
        }
        return j;
    }

    private static int CastBool1(BitArray arg) {
        int j = 0;
        var ba = arg.Cast<bool>();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int TypeSafeEnumerator1(BitArray arg) {
        int j = 0;
        var ba = arg.GetTypeSafeEnumerator();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int UseToList1(BitArray arg) {
        int j = 0;
        var ba = arg.ToList();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int PlainFor(BitArray arg) {
        int j = 0;
        for (int i = 0; i < arg.Length; i++) {
            j += arg[i] ? 1 : 0;
        }
        return j;
    }

    private static int ForEachBool(BitArray arg) {
        int j = 0;
        foreach (bool b in arg) {
            j += b ? 1 : 0;                
        }
        return j;
    }

    private static int CastBool(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.Cast<bool>()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    private static int TypeSafeEnumerator(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.GetTypeSafeEnumerator()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    private static int UseToList(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.ToList()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    public static List<bool> ToList(this BitArray ba) {
        List<bool> l = new List<bool>(ba.Count);
        for (int i = 0; i < ba.Count; i++) {
            l.Add(ba[i]);
        }
        return l;
    }

    public static IEnumerable<bool> GetTypeSafeEnumerator(this BitArray ba) {
        for (int i = 0; i < ba.Length; i++)
            yield return ba[i];
    }
}

结果(名称,迭代次数,总耗时,得分(得分越高越差)):

============ Different looping methods ============
Plain for loop                                        456899 0:28.087 1,00
foreach(bool bit in bitArray)                         135799 0:29.188 3,50
foreach(bool bit in bitArray.Cast<bool>)               81948 0:33.183 6,59
foreach(bool bit in bitArray.GetTypeSafeEnumerator()) 179956 0:27.508 2,49
foreach(bool bit in bitArray.ToList())                161883 0:27.793 2,79

============ Avoiding repeated conversions ============
Plain for loop                                        5381 0:33.247 1,00
foreach(bool bit in bitArray.Cast<bool>)               745 0:28.273 6,14
foreach(bool bit in bitArray.GetTypeSafeEnumerator()) 2304 0:27.457 1,93
foreach(bool bit in bitArray.ToList())                4603 0:30.583 1,08

也许我的基准测试方式有些不对。当使用MiniBench和你的测试时,我得到了相当相似的结果(尽管迭代次数只有四分之一)。MiniBench非常好用。 - Joel B Fant

2
如果存在的话,你会向BitArray<T>传递什么样的通用类型参数的示例? BitArray的定义如下:
管理由布尔值表示的比特值的紧凑数组, 其中true表示开(1),false表示关(0)。
这种类型是一个优化后的位数组,没有其他价值。它没有成员可以从类型中分离出来,因此没有将其设为通用类型的必要性。任何像这样的专门集合都可以被视为某个父通用集合的闭合构造类型。换句话说,BitArray有点像List<Boolean>(当然添加了许多有用的方法)。
编辑:是的,这种类型实现了 IEnumerable,但没有实现 IEnumerable<T> - 这很可能是因为它是一个旧类型,没有更新。请记住,您可以使用 Enumerable.Cast<TResult> 来解决这个问题:
yourBitArray.Cast<bool>();

准确地说,接口是布尔类型。存储以位为单位(无关紧要)。 - H H
1
BitArray类型确实是类型安全的。它唯一的缺点是它没有实现任何通用接口(BitArray的所有方法:Get,Set等都是强类型的)。但这可以通过从Enumerable类中使用扩展方法来解决。 - Andrew Hare
1
请记住,使用 Cast<T>() 仍会导致 bool 值的装箱和拆箱。这可能是您关心的问题,也可能不是。 - Randolpho
2
@Joan,由于该类型未实现“IEnumerable<T>”,因此无法在不将每个值拆箱为“Boolean”的情况下迭代“BitArray”。 - Andrew Hare
1
@Randolpho - 但是Joel B Fant的解决方案也没有使用枚举器,因此不会迭代BitArray :) - Andrew Hare
显示剩余5条评论

1

你为什么需要一个通用版本呢?除了位或被转换为位的布尔值,BitArray可能使用哪种类型呢?

更新: 它是类型安全的。如果你正在使用foreach(var bit in bitArray),那么bit将出现为一个对象,但你同样可以使用foreach(bool bit in bitArray)。这适用于所有实现IEnumerable而不是IEnumerable<T>的集合。


但是如果你使用foreach(bool bit in bitArray),那么会自动进行类型转换吗? - Joan Venge
是的。它是“类型安全”的,因为foreach会为您执行转换,并且您已经知道BitArray始终会传递bool给您。然而,它不是通用的,因此每个foreach都会进行装箱和拆箱。 - Randolpho

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