创建一个不复制C#的ImmutableArray

时间:2018-07-12 07:49:06

标签: c# optimization

http://source.roslyn.io/#System.Collections.Immutable/System/Collections/Immutable/ImmutableArray.cs上查看ImmutableArray的源代码

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

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

有什么办法(可能是一个肮脏的hack)来创建一个ImmutableArray,它只会使用我原来的数组,而不是复制过来?

编辑

恰好看到了此功能的要求。它还提供了最快的黑客使用方法:https://github.com/dotnet/corefx/issues/28064

5 个答案:

答案 0 :(得分:6)

如果知道数组的确切长度,则可以使用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())将创建数组的副本。

答案 1 :(得分:2)

还有另外两种骇人听闻的方法,都在这里建议: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. 不安全的投射(好帮手written here,在this blog post中找到)。投射使用System.Runtime.CompilerServices.Unsafe NuGet

    中可用的Unsafe静态类
    using System.Runtime.CompilerServices;
    
    static ImmutableArray<T> HackyMakeImmutable<T>(T[] array)
    {
        return Unsafe.As<T[], ImmutableArray<T>>(ref array);
    }
    

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

答案 2 :(得分:1)

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

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成本权衡。

答案 3 :(得分: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>...
}

现在您可以将包装程序安全地传递给客户了。

答案 4 :(得分:0)

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

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="modal-content">
  <div class="modal-header">...</div>
  <div class="modal-body">
    <textarea class="form-control" rows="10" placeholder='comment...'></textarea>
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
    <button type="button" class="btn btn-primary modal-save" data-dismiss="modal">Save changes</button>
  </div>