3

有没有办法(可能是一个肮脏的黑客)来创建一个只使用指定数组而不是复制它的 ImmutableArray?

我有一个我知道不会更改的数组,我想创建一个 ImmutableArray 以允许客户端安全地访问我的数组。

查看 ImmutableArray的源代码,我发现 Create 方法无济于事:

public static ImmutableArray<T> Create<T>(params T[] items)
{
    if (items == null)
    {
        return Create<T>();
    }

    // We can't trust that the array passed in will never be mutated by the caller.
    // The caller may have passed in an array explicitly (not relying on compiler params keyword)
    // and could then change the array after the call, thereby violating the immutable
    // guarantee provided by this struct. So we always copy the array to ensure it won't ever change.
    return CreateDefensiveCopy(items);
}

编辑:在 GitHub 上有一个关于这个特性的请求,它也提供了最快的 hack 使用:https ://github.com/dotnet/corefx/issues/28064

4

6 回答 6

12

如果您知道数组的确切长度,则可以使用ImmutableArray.CreateBuilder<>加号从内部.MoveToImmutable()创建一个而不复制它:ImmutableArray<>Builder

var builder = ImmutableArray.CreateBuilder<int>(4);
builder.Add(1);
builder.Add(2);
builder.Add(3);
builder.Add(4);
ImmutableArray<int> array = builder.MoveToImmutable();

该方法.MoveToImmutable()将抛出异常,如果builder.Capacity != builder.Count

请注意,构建器的其他方法(如.ToImmutable())将创建数组的副本。

于 2018-07-12T08:01:54.557 回答
3

还有另外两种 hacky 方法,都在这里建议:https ://stackoverflow.com/a/3799030/4418060 (一个在回答,一个在评论)。

  1. 将一种结构类型编组为另一种。
  2. 不安全地将一个转换为另一个。

第一个涉及创建一个反映 ImmutableArray 布局的新结构类型(它是单个T[]字段)并更改 CLR(运行时)看到的该结构的类型。该结构将如下所示:

public struct HackImmutableArray<T>
{
    public T[] Array;
}
  1. 编组:

    static ImmutableArray<T> HackyMakeImmutable<T>(T[] array)
    {
        var arrayObject = (object)new HackImmutableArray<T> { Array = array };
        var handle = GCHandle.Alloc(arrayObject, GCHandleType.Pinned);
        var immutable = (ImmutableArray<T>)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return immutable;
    }
    
  2. 不安全的铸造(这里写的好帮手,在这篇博文中找到)。铸造使用System.Runtime.CompilerServices.Unsafe NuGetUnsafe中可用的静态类

    using System.Runtime.CompilerServices;
    
    static ImmutableArray<T> HackyMakeImmutable<T>(T[] array)
    {
        return Unsafe.As<T[], ImmutableArray<T>>(ref array);
    }
    

第二个选项是“不安全”但非常安全,因为我们可以肯定地假设 ImmutableArray 的结构布局不会改变,这是一个定义特性,而且它也可能比任何其他解决方案都要快得多。

于 2018-07-12T09:15:45.997 回答
2

https://github.com/dotnet/corefx/issues/28064他们推荐最快的方法是使用 System.Runtime.CompilerServices.Unsafe:

ImmutableArray<T> im = Unsafe.As<T[], ImmutableArray<T>>(ref array);
于 2018-07-12T09:14:06.593 回答
0

这可能是个坏主意,他们可以对您使用相同的技巧,但您可以通过反射作弊:

public static ImmutableArray<T> GetImmutableArray<T>(T[] arr)
{
    var immutableArray = ImmutableArray.Create(new T[0]);
    var boxed = ((object) immutableArray);
    var t = boxed.GetType();
    var fi = t.GetField("array", BindingFlags.NonPublic | BindingFlags.Instance);
    fi.SetValue(boxed, arr);
    return (ImmutableArray<T>)boxed;
}

并这样称呼它:

var arr = new int[] { 1, 2, 3 };
Console.WriteLine("Arr: " + string.Join(",", arr)); //Arr: 1,2,3
var imm = GetImmutableArray(arr);
Console.WriteLine("ImmutableArray: " + string.Join(",", imm)); //ImmutableArray: 1,2,3
arr[0] = 234;
imm[0] = 235; //Compile Error
Console.WriteLine("ImmutableArray: " + string.Join(",", imm)); //ImmutableArray: 234,2,3

反射成本必须与 Array.Copy 成本进行权衡。

于 2018-07-12T08:27:42.387 回答
0

你需要一个ImmutableArray<T>还是IReadOnlyList<T>足够?如果是后者,您总是可以实现一个非常轻量级的数组包装器来满足您的需求:

public class ImmutableArrayWrapper<T>: IReadOnlyList<T>
{
     public static ImmutableArrayWrapper<T> Wrap(T[] array)
         => new ImmutableArrayWrapper(array);

    private readonly T[] innerArray;

    private ImmutableArrayWrapper(T[] arr) {
         if (arr == null)
             throw new ArgumentNullException();

         innerArray = arr; }

    public int Count => innerArray.Count();
    public T this[int index] => innerArray[index];

    //IEnumerable<T>...
}

现在您可以安全地将包装器传递给您的客户。

于 2018-07-12T08:42:03.023 回答
0

ReadOnlyCollection<T>在许多情况下可以用来达到相同的目的。它提供对原始数组的访问权限是不正确的——该Items属性受到保护。

ReadOnlyCollection<T>但是,使用而不是有两个缺点ImmutableArray<T>

  1. 发生额外分配。ReadOnlyCollection<T>是一个包装 an 的类IList<T>,而ImmutableArray<T>是一个包装 an 的结构体T[]
  2. 集合的接收者对不变性的保证较弱。集合不能从外部修改,但是创建它的人可能仍然持有对原始数组的引用,并且可以使用它来修改集合。

文档可以提供不会发生此类修改的保证,但它仍然比技术上强制执行的保证要弱一些。

于 2021-09-15T18:12:52.203 回答