├── .gitignore ├── LICENSE ├── README.md └── src ├── Test ├── Test.csproj ├── UnmanagedArrayTest.cs └── UnmanagedListTest.cs ├── UnmanagedArray.sln └── UnmanagedArray ├── ThrowHelper.cs ├── UnmanagedArray.cs ├── UnmanagedArray.csproj └── UnmanagedList.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | # Custom (only for this project) 264 | src/ElffyResourceCompiler/Resources 265 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ikorin24 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unmanaged Array 2 | 3 | [![GitHub license](https://img.shields.io/github/license/ikorin24/UnmanagedArray?color=FA77FF)](https://github.com/ikorin24/UnmanagedArray/blob/master/LICENSE) 4 | [![nuget](https://img.shields.io/badge/nuget-v2.1.3-FA77FF)](https://www.nuget.org/packages/UnmanagedArray) 5 | 6 | An Effective tool for unmanaged array in C#. 7 | 8 | ## About 9 | 10 | Array in C# is allocated in managed memory. 11 | 12 | ```UnmanagedArray``` in this library is allocated in unmanaged memory. 13 | 14 | In other words, items in `UnmanagedArray` is not collected by Garbage Collection. 15 | 16 | ### Supported Types 17 | 18 | The only type of item `UnmanagedArray` supports is `unmanaged` type. 19 | 20 | `unmanaged` type is `int`, `float`, recursive-unmanaged struct, and so on. 21 | 22 | `string`, and other types which are `class` are NOT SUPPORTED. 23 | 24 | (because reference types are allocated in managed memory on C#.) 25 | 26 | ## Building from Source 27 | 28 | ```sh 29 | $ git clone https://github.com/ikorin24/UnmanagedArray.git 30 | $ cd UnmanagedArray 31 | $ dotnet build src/UnmanagedArray/UnmanagedArray.csproj -c Release 32 | ``` 33 | 34 | ## Installation 35 | 36 | Install from Nuget by package manager console (in Visual Studio). 37 | 38 | https://www.nuget.org/packages/UnmanagedArray 39 | 40 | ``` 41 | PM> Install-Package UnmanagedArray 42 | ``` 43 | 44 | ## How to Use 45 | 46 | The way of use is similar to normal array. 47 | 48 | Unmanaged resources are release when an instance goes through the scope. 49 | 50 | ```cs 51 | using UnmanageUtility; 52 | 53 | // UnmanagedArray releases its memories when it goes through the scope. 54 | using(var array = new UnmanagedArray(10)) 55 | { 56 | for(int i = 0;i < array.Length;i++) 57 | { 58 | array[i] = i; 59 | } 60 | } 61 | ``` 62 | 63 | If not use the `using` scope, you can release the memories by `Dispose()` method. 64 | 65 | ```cs 66 | var array = new UnmanagedArray(10); 67 | array[3] = 100; 68 | array.Dispose(); // The memories allocated in unmanaged is released here. 69 | ``` 70 | 71 | Of cource, LINQ is supported. 72 | 73 | ```cs 74 | using(var array = Enumerable.Range(0, 10).ToUnmanagedArray()) 75 | using(var array2 = array.Where(x => x >= 5).ToUnmanagedArray()) { 76 | for(int i = 0; i < array2.Length; i++) { 77 | Console.WriteLine(array2[i]); // 5, 6, 7, 8, 9 78 | } 79 | } 80 | ``` 81 | 82 | ***NOTICE*** 83 | 84 | `UnmanagedArray` has Finalizer and releases its unmanaged resources automatically when you forget releasing that. 85 | 86 | However, you have to release them explicitly ( by `using` scope or `Dispose()` ). 87 | 88 | ### New Feature of ver 2.1.0 89 | 90 | `UnmanagedList` is available, which the way of use is similar to `List`. 91 | 92 | ```cs 93 | using(var list = new UnmanagedList()) 94 | { 95 | list.Add(4); 96 | list.Add(9); 97 | foreach(var num in list) 98 | { 99 | Console.WriteLine(num); 100 | } 101 | } 102 | ``` 103 | 104 | ## License and Credits 105 | 106 | This is under [MIT license](https://github.com/ikorin24/UnmanagedArray/blob/master/LICENSE). 107 | 108 | This software includes the work that is distributed in the Apache License 2.0. 109 | 110 | Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 111 | 112 | ## Author 113 | 114 | [github: ikorin24](https://github.com/ikorin24) 115 | 116 | ## Release Note 117 | 118 | ### 2020/01/11 v1.0.0 119 | 120 | [![nuget](https://img.shields.io/badge/nuget-v1.0.0-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/1.0.0) 121 | 122 | - First release 123 | 124 | ### 2020/01/12 v1.0.1 125 | 126 | [![nuget](https://img.shields.io/badge/nuget-v1.0.1-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/1.0.1) 127 | 128 | - Performance improvement of iteration by `foreach`, that is as faster as T[] (normal array). 129 | 130 | ### 2020/01/15 v1.0.2 131 | 132 | [![nuget](https://img.shields.io/badge/nuget-v1.0.2-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/1.0.2) 133 | 134 | - Great performance improvement of accessing to the item by index. (`array[i]`) 135 | 136 | ### 2020/04/30 v1.0.3 137 | 138 | [![nuget](https://img.shields.io/badge/nuget-v1.0.3-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/1.0.3) 139 | 140 | - Performance improvement. 141 | - Add `GC.AddMemoryPressure` in constructor and `GC.RemoveMemoryPressure` in destructor. 142 | 143 | ### 2020/04/30 v2.0.0 144 | 145 | [![nuget](https://img.shields.io/badge/nuget-v2.0.0-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.0.0) 146 | 147 | - Change namespace into `UnmanageUtility`. 148 | 149 | ### 2020/06/07 v2.0.1 150 | 151 | [![nuget](https://img.shields.io/badge/nuget-v2.0.1-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.0.1) 152 | 153 | ### 2020/07/27 v2.1.0-rc 154 | 155 | [![nuget](https://img.shields.io/badge/nuget-v2.1.0_rc-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.1.0-rc) 156 | 157 | - Add `UnmanagedList`. 158 | 159 | ### 2020/10/05 v2.1.0 160 | 161 | [![nuget](https://img.shields.io/badge/nuget-v2.1.0-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.1.0) 162 | 163 | - Performance improvement. 164 | - Add some methods. 165 | 166 | ### 2020/11/26 v2.1.1 167 | 168 | [![nuget](https://img.shields.io/badge/nuget-v2.1.1-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.1.1) 169 | 170 | - Add `UnmanagedArray.Empty` static property. 171 | - Add property setter of `UnmanagedList.Capacity`. 172 | 173 | ### 2021/01/05 v2.1.2 174 | 175 | [![nuget](https://img.shields.io/badge/nuget-v2.1.2-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.1.2) 176 | 177 | - Add `UnmanagedArray.AsSpan` overload methods. 178 | - Package for multi target frameworks. (net48, netcoreapp3.1, net5.0, netstandard2.0, netstandard2.1) 179 | - Fix small bugs. 180 | 181 | ### 2021/02/10 v2.1.3 182 | 183 | [![nuget](https://img.shields.io/badge/nuget-v2.1.3-FA77FF)](https://www.nuget.org/packages/UnmanagedArray/2.1.3) 184 | 185 | - Add `UnmanagedList.Extend` method. 186 | -------------------------------------------------------------------------------- /src/Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;netcoreapp3.1;net48 5 | 9.0 6 | enable 7 | true 8 | 9 | Library 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Test/UnmanagedArrayTest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 ikorin24 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #nullable enable 27 | using Xunit; 28 | using System; 29 | using System.Collections.Generic; 30 | using System.Runtime.InteropServices; 31 | using System.Linq; 32 | using System.Collections; 33 | using UnmanageUtility; 34 | 35 | namespace Test 36 | { 37 | public class UnmanagedArrayTest 38 | { 39 | [Fact] 40 | public unsafe void Exception() 41 | { 42 | using(var array = new UnmanagedArray(10000)) { 43 | Assert.Throws(() => array[-1] = 4); 44 | Assert.Throws(() => array[-8]); 45 | Assert.Throws(() => array[array.Length] = 9); 46 | Assert.Throws(() => array[array.Length]); 47 | 48 | // CopyTo 49 | Assert.Throws(() => array.CopyTo(null!, 0)); 50 | Assert.Throws(() => array.CopyTo(new int[array.Length], -1)); 51 | Assert.Throws(() => array.CopyTo(new int[array.Length], array.Length)); 52 | Assert.Throws(() => array.CopyTo(new int[array.Length], 10)); // not enough length of destination 53 | Assert.Throws(() => array.CopyTo(new int[100], 0)); // not enough length of destination 54 | 55 | // CopyFrom 56 | Assert.Throws(() => array.CopyFrom(new ReadOnlySpan(), -1)); 57 | Assert.Throws(() => array.CopyFrom(new int[10000], 5)); 58 | Assert.Throws(() => array.CopyFrom(IntPtr.Zero, 0, 4)); 59 | var ptr = stackalloc int[10]; 60 | Assert.Throws(() => array.CopyFrom((IntPtr)ptr, -1, 5)); 61 | Assert.Throws(() => array.CopyFrom((IntPtr)ptr, 5, -1)); 62 | Assert.Throws(() => array.CopyFrom((IntPtr)ptr, 9999, 4)); 63 | 64 | // NotSupported methods 65 | Assert.Throws(() => (array as IList).Insert(0, 0)); 66 | Assert.Throws(() => (array as IList).RemoveAt(0)); 67 | Assert.Throws(() => (array as ICollection).Add(0)); 68 | Assert.Throws(() => (array as ICollection).Remove(0)); 69 | Assert.Throws(() => (array as ICollection).Clear()); 70 | Assert.Throws(() => (array as IList).Add(0)); 71 | Assert.Throws(() => (array as IList).Clear()); 72 | Assert.Throws(() => (array as IList).Insert(0, 0)); 73 | Assert.Throws(() => (array as IList).Remove(0)); 74 | Assert.Throws(() => (array as IList).RemoveAt(0)); 75 | } 76 | Assert.Throws(() => (null as int[])!.ToUnmanagedArray()); 77 | Assert.Throws(() => new UnmanagedArray(-4)); 78 | Assert.Throws(() => new UnmanagedArray(-5, true)); 79 | } 80 | 81 | [Fact] 82 | public void Ptr() 83 | { 84 | using(var array = new UnmanagedArray(10)) { 85 | Assert.NotEqual(array.Ptr, IntPtr.Zero); 86 | } 87 | } 88 | 89 | [Fact] 90 | public void Length() 91 | { 92 | for(int i = 0; i < 1000; i++) { 93 | using(var array = new UnmanagedArray(i)) { 94 | Assert.Equal(array.Length, i); 95 | } 96 | } 97 | } 98 | 99 | [Fact] 100 | public void Empty() 101 | { 102 | var array = UnmanagedArray.Empty; 103 | Assert.Equal(0, array.Length); 104 | Assert.True(array.IsDisposed); // empty array is disposed already 105 | Assert.Equal(IntPtr.Zero, array.Ptr); // pointer of empty array is always null. 106 | 107 | var array2 = UnmanagedArray.Empty; 108 | Assert.True(ReferenceEquals(array, array2)); // empty arrays are same instance. 109 | 110 | // Nothing happens if dispose empty array 111 | array.Dispose(); 112 | array.Dispose(); 113 | 114 | Assert.Equal(0, array.Length); 115 | Assert.True(array.IsDisposed); 116 | Assert.Equal(IntPtr.Zero, array.Ptr); 117 | } 118 | 119 | [Fact] 120 | public void Indexer() 121 | { 122 | using(var array = new UnmanagedArray(100)) { 123 | for(int i = 0; i < array.Length; i++) { 124 | array[i] = i * i; 125 | } 126 | for(int i = 0; i < array.Length; i++) { 127 | Assert.Equal(array[i], i * i); 128 | } 129 | } 130 | 131 | using(var array = new UnmanagedArray(10)) { 132 | for(int i = 0; i < array.Length; i++) { 133 | array[i] = i; 134 | } 135 | for(int i = 0; i < array.Length; i++) { 136 | Assert.Equal(i, (array as IList)[i]); 137 | } 138 | } 139 | } 140 | 141 | [Fact] 142 | public void Constructor() 143 | { 144 | using var array = new UnmanagedArray(10); 145 | Assert.Equal(10, array.Length); 146 | Span span = stackalloc ushort[10]; 147 | using var array2 = new UnmanagedArray(span); 148 | Assert.Equal(10, array2.Length); 149 | using var array3 = new UnmanagedArray(30, 300L); 150 | for(int i = 0; i < array3.Length; i++) { 151 | Assert.Equal(300L, array3[i]); 152 | } 153 | } 154 | 155 | [Fact] 156 | public void OtherProperties() 157 | { 158 | using(var array = new UnmanagedArray(30)) { 159 | Assert.True((array as ICollection).IsReadOnly); 160 | Assert.False((array as IList).IsReadOnly); 161 | Assert.True((array as IList).IsFixedSize); 162 | Assert.Equal(30, (array as ICollection).Count); 163 | Assert.Equal(30, (array as IReadOnlyCollection).Count); 164 | Assert.Equal(30, (array as ICollection).Count); 165 | Assert.NotNull((array as ICollection).SyncRoot); 166 | Assert.False((array as ICollection).IsSynchronized); 167 | } 168 | } 169 | 170 | [Fact] 171 | public void ArrayDispose() 172 | { 173 | var array = new UnmanagedArray(10); 174 | array.Dispose(); 175 | Assert.True(array.IsDisposed); 176 | Assert.Equal(IntPtr.Zero, array.Ptr); 177 | Assert.Equal(0, array.Length); 178 | Assert.True(array.AsSpan().IsEmpty); 179 | Assert.Throws(() => array[0] = 34f); 180 | Assert.Throws(() => array[3]); 181 | Assert.Throws(() => array.GetReference() = 3f); 182 | Assert.Throws(() => array.GetReference()); 183 | Assert.Throws(() => array.GetReference(2) = 3f); 184 | Assert.Throws(() => array.GetReference(5)); 185 | //Assert.Throws(() => ((ICollection)array).Count); 186 | //Assert.Throws(() => ((IReadOnlyCollection)array).Count); 187 | //Assert.Throws(() => ((ICollection)array).Count); 188 | Assert.Throws(() => ((IList)array)[0]); 189 | Assert.Throws(() => ((IList)array)[0] = 0f); 190 | Assert.Throws(() => array.GetEnumerator()); 191 | Assert.Throws(() => ((IEnumerable)array).GetEnumerator()); 192 | Assert.Throws(() => ((IEnumerable)array).GetEnumerator()); 193 | Assert.Throws(() => array.IndexOf(0f)); 194 | Assert.Throws(() => array.Contains(0f)); 195 | Assert.Throws(() => array.CopyTo(new float[10], 0)); 196 | #pragma warning disable CS0618 // warning for obsolete 197 | Assert.Throws(() => array.GetPtrIndexOf(0)); 198 | #pragma warning restore CS0618 // warning for obsolete 199 | Assert.Throws(() => 200 | { 201 | using var array2 = new UnmanagedArray(10); 202 | array.CopyFrom(array2.Ptr, 0, array2.Length); 203 | }); 204 | Assert.Throws(() => array.CopyFrom(new UnmanagedArray(10))); 205 | Assert.Throws(() => 206 | { 207 | var span = new Span(); 208 | array.CopyFrom(span, 0); 209 | }); 210 | 211 | Assert.Throws(() => array.AsSpan(-1)); 212 | Assert.Throws(() => array.AsSpan(-1, 0)); 213 | Assert.Throws(() => array.AsSpan(0, -1)); 214 | Assert.Throws(() => array.AsSpan(0, array.Length + 1)); 215 | Assert.Throws(() => array.AsSpan(1, array.Length)); 216 | 217 | 218 | array.Dispose(); // No exception although already disposed 219 | } 220 | 221 | [Fact] 222 | public void GetReference() 223 | { 224 | using(var array = new UnmanagedArray(10)) { 225 | array[0] = 31; 226 | array[8] = 40; 227 | ref var head = ref array.GetReference(); 228 | Assert.Equal(31, head); 229 | head = 20; 230 | Assert.Equal(20, head); 231 | 232 | ref var item = ref array.GetReference(8); 233 | Assert.Equal(40, item); 234 | item = 600; 235 | Assert.Equal(600, item); 236 | 237 | Assert.Throws(() => array.GetReference(30)); 238 | Assert.Throws(() => array.GetReference(50) = 100); 239 | Assert.Throws(() => array.GetReference(-1)); 240 | Assert.Throws(() => array.GetReference(-3) = 100); 241 | } 242 | } 243 | 244 | [Fact] 245 | public void ToUnmanagedArray() 246 | { 247 | var rand = new Random(12345678); 248 | var origin = Enumerable.Range(0, 100).Select(i => rand.Next()).ToArray(); 249 | 250 | // from Array 251 | using(var array = origin.ToUnmanagedArray()) { 252 | Assert.Equal(origin.Length, array.Length); 253 | for(int i = 0; i < array.Length; i++) { 254 | Assert.Equal(array[i], origin[i]); 255 | } 256 | } 257 | 258 | // From ICollection 259 | var list = origin.ToList(); 260 | using(var array = list.ToUnmanagedArray()) { 261 | Assert.Equal(list.Count, array.Length); 262 | for(int i = 0; i < array.Length; i++) { 263 | Assert.Equal(array[i], list[i]); 264 | } 265 | } 266 | 267 | // From IEnumerable 268 | using(var array = Enumerable.Range(0, 10).ToUnmanagedArray()) { 269 | Assert.Equal(10, array.Length); 270 | for(int i = 0; i < array.Length; i++) { 271 | Assert.Equal(array[i], i); 272 | } 273 | } 274 | 275 | // From Empty IEnumerable 276 | using(var array = Enumerable.Empty().ToUnmanagedArray()) { 277 | Assert.Equal(0, array.Length); 278 | } 279 | } 280 | 281 | [Fact] 282 | public void Enumerate() 283 | { 284 | using(var array = new UnmanagedArray(100)) { 285 | foreach(var item in array) { 286 | Assert.False(item); 287 | } 288 | for(int i = 0; i < array.Length; i++) { 289 | array[i] = true; 290 | } 291 | foreach(var item in array) { 292 | Assert.True(item); 293 | } 294 | } 295 | } 296 | 297 | [Fact] 298 | public void Linq() 299 | { 300 | using(var array = new UnmanagedArray(100)) { 301 | array.All(x => !x); 302 | for(int i = 0; i < array.Length; i++) { 303 | array[i] = true; 304 | } 305 | array.All(x => x); 306 | var rand1 = new Random(1234); 307 | var rand2 = new Random(1234); 308 | var seq1 = array.Select(x => rand1.Next()); 309 | var seq2 = array.Select(x => rand2.Next()); 310 | Assert.True(seq1.SequenceEqual(seq2)); 311 | } 312 | } 313 | 314 | [Fact] 315 | public void IndexOf() 316 | { 317 | using(var array = Enumerable.Range(10, 10).ToUnmanagedArray()) { 318 | Assert.Equal(4, array.IndexOf(14)); 319 | Assert.Equal(-1, array.IndexOf(120)); 320 | Assert.Equal(5, ((IList)array).IndexOf(15)); 321 | Assert.Equal(-1, ((IList)array).IndexOf(140)); 322 | Assert.Equal(-1, ((IList)array).IndexOf(new object())); 323 | Assert.Equal(-1, ((IList)array).IndexOf(null!)); 324 | } 325 | } 326 | 327 | [Fact] 328 | public void Contains() 329 | { 330 | using(var array = Enumerable.Range(10, 10).ToUnmanagedArray()) 331 | using(var array2 = new UnmanagedArray(array.Length)) { 332 | var tmp = array.Contains(179); 333 | Assert.False(tmp); 334 | var tmp2 = array.Contains(16); 335 | Assert.True(tmp2); 336 | } 337 | } 338 | 339 | [Fact] 340 | public void CopyTo() 341 | { 342 | using(var array = new UnmanagedArray(10)) { 343 | var dest = new int[array.Length + 5]; 344 | array.CopyTo(dest, 5); 345 | Assert.True(dest.Skip(5).SequenceEqual(array)); 346 | } 347 | 348 | using(var array = new UnmanagedArray(20)) { 349 | var dest = new float[array.Length + 8]; 350 | ((ICollection)array).CopyTo(dest, 8); 351 | Assert.True(dest.Skip(8).SequenceEqual(array)); 352 | } 353 | } 354 | 355 | [Fact] 356 | [Obsolete] 357 | public unsafe void GetPtr() 358 | { 359 | using(var array = new UnmanagedArray(10)) { 360 | Assert.Equal(array.Ptr, array.GetPtrIndexOf(0)); 361 | for(int i = 0; i < array.Length; i++) { 362 | var p1 = array.Ptr + sizeof(long) * i; 363 | var p2 = array.GetPtrIndexOf(i); 364 | var p3 = (IntPtr)(&((long*)array.Ptr)[i]); 365 | Assert.Equal(p1, p2); 366 | Assert.Equal(p1, p3); 367 | } 368 | } 369 | } 370 | 371 | [Fact] 372 | public void AsSpan() 373 | { 374 | using(var array = Enumerable.Range(0, 100).ToUnmanagedArray()) { 375 | var answer = Enumerable.Range(0, 100).Sum(); 376 | var span = array.AsSpan(); 377 | var sum = 0; 378 | foreach(var item in span) { 379 | sum += item; 380 | } 381 | Assert.Equal(answer, sum); 382 | 383 | for(int i = 0; i < span.Length; i++) { 384 | span[i] = 30; 385 | } 386 | Assert.True(array.All(x => x == 30)); 387 | } 388 | 389 | using(var array = Enumerable.Range(0, 100).ToUnmanagedArray()) { 390 | var ans = Enumerable.Range(0, 100).ToArray(); 391 | Assert.True(array.AsSpan().SequenceEqual(ans)); 392 | Assert.True(array.AsSpan(0).SequenceEqual(ans.AsSpan(0))); 393 | Assert.True(array.AsSpan(100).SequenceEqual(ans.AsSpan(100))); 394 | Assert.True(array.AsSpan(0, 100).SequenceEqual(ans.AsSpan(0, 100))); 395 | Assert.True(array.AsSpan(0, 0).SequenceEqual(ans.AsSpan(0, 0))); 396 | Assert.True(array.AsSpan(0, 0).SequenceEqual(ans.AsSpan(0, 0))); 397 | Assert.True(array.AsSpan(10, 20).SequenceEqual(ans.AsSpan(10, 20))); 398 | 399 | 400 | Assert.True(array.AsSpan().SequenceEqual(array.AsSpan(0, array.Length))); 401 | } 402 | 403 | using(var array = UnmanagedArray.Empty) { 404 | Assert.True(array.AsSpan().IsEmpty); 405 | Assert.True(array.AsSpan(0).IsEmpty); 406 | Assert.True(array.AsSpan(0, 0).IsEmpty); 407 | } 408 | } 409 | 410 | [Fact] 411 | public void CopyFrom() 412 | { 413 | Span span = stackalloc bool[20]; 414 | for(int i = 0; i < span.Length; i++) { 415 | span[i] = true; 416 | } 417 | 418 | using(var array = new UnmanagedArray(span.Length)) { 419 | array.CopyFrom(span); 420 | for(int i = 0; i < array.Length; i++) { 421 | Assert.True(array[i]); 422 | } 423 | } 424 | using(var array = new UnmanagedArray(span.Length + 20)) { 425 | array.CopyFrom(span, 5); 426 | for(int i = 0; i < array.Length; i++) { 427 | if(i < 5 || i >= 5 + span.Length) { 428 | Assert.False(array[i]); 429 | } 430 | else { 431 | Assert.True(array[i]); 432 | } 433 | } 434 | } 435 | 436 | using(var array = new UnmanagedArray(span)) 437 | using(var array2 = new UnmanagedArray(array.Length + 20)) 438 | using(var array3 = new UnmanagedArray(array.Length + 20)) { 439 | array2.CopyFrom(array); 440 | for(int i = 0; i < array2.Length; i++) { 441 | Assert.Equal(i < array.Length, array2[i]); 442 | } 443 | 444 | array3.CopyFrom(array.Ptr, 4, array.Length); 445 | for(int i = 0; i < array3.Length; i++) { 446 | 447 | if(i < 4 || i >= 4 + array.Length) { 448 | Assert.False(array3[i]); 449 | } 450 | else { 451 | Assert.True(array3[i]); 452 | } 453 | } 454 | } 455 | 456 | // Copy from Empty 457 | using(var array = new UnmanagedArray(10)) { 458 | var empty = Enumerable.Empty().ToArray(); 459 | unsafe { 460 | fixed(short* ptr = empty) { 461 | array.CopyFrom((IntPtr)ptr, 0, empty.Length); 462 | } 463 | } 464 | for(int i = 0; i < array.Length; i++) { 465 | Assert.Equal(0, array[i]); 466 | } 467 | } 468 | } 469 | 470 | [Fact] 471 | public void CreateFromStruct() 472 | { 473 | var data = new TestStruct() 474 | { 475 | A = 10, 476 | B = 5, 477 | C = 90, 478 | D = new TestSubStruct() 479 | { 480 | A = 32, 481 | B = 50, 482 | C = 0xAABBCCDDEEFF0011, 483 | } 484 | }; 485 | using(var array = UnmanagedArray.CreateFromStruct(ref data)) { 486 | Assert.Equal(10, array[0]); 487 | Assert.Equal(5, array[1]); 488 | Assert.Equal(90, array[2]); 489 | Assert.Equal(32, array[3]); 490 | Assert.Equal(50, array[4]); 491 | Assert.Equal(0xEEFF0011, array[5]); 492 | Assert.Equal(0xAABBCCDD, array[6]); 493 | } 494 | 495 | var empty = new EmptyStruct(); 496 | using(var array = UnmanagedArray.CreateFromStruct(ref empty)) { 497 | 498 | } 499 | } 500 | 501 | 502 | [StructLayout(LayoutKind.Explicit)] 503 | struct TestStruct 504 | { 505 | [FieldOffset(0)] 506 | public int A; 507 | [FieldOffset(4)] 508 | public int B; 509 | [FieldOffset(8)] 510 | public int C; 511 | [FieldOffset(12)] 512 | public TestSubStruct D; 513 | } 514 | 515 | [StructLayout(LayoutKind.Explicit)] 516 | struct TestSubStruct 517 | { 518 | [FieldOffset(0)] 519 | public int A; 520 | [FieldOffset(4)] 521 | public int B; 522 | [FieldOffset(8)] 523 | public ulong C; 524 | } 525 | 526 | [StructLayout(LayoutKind.Explicit)] 527 | struct EmptyStruct 528 | { 529 | 530 | } 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /src/Test/UnmanagedListTest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 ikorin24 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #nullable enable 27 | using System; 28 | using System.Collections.Generic; 29 | using System.Linq; 30 | using System.Runtime.CompilerServices; 31 | using System.Runtime.InteropServices; 32 | using System.Text; 33 | using System.Threading.Tasks; 34 | using UnmanageUtility; 35 | using Xunit; 36 | 37 | namespace Test 38 | { 39 | public unsafe class UnmanagedListTest 40 | { 41 | [Fact] 42 | public void Ctor() 43 | { 44 | static void Ctor_() where T : unmanaged 45 | { 46 | // default ctor 47 | using(var list = new UnmanagedList()) { 48 | Assert.True(list.Capacity > 0); 49 | Assert.True(list.Count == 0); 50 | Assert.True(list.Ptr == IntPtr.Zero); 51 | } 52 | 53 | // ctor of setting capacity 54 | using(var list = new UnmanagedList(10)) { 55 | Assert.True(list.Capacity == 10); 56 | Assert.True(list.Count == 0); 57 | Assert.True(list.Ptr == IntPtr.Zero); 58 | } 59 | 60 | // ctor of setting empty capacity 61 | using(var list = new UnmanagedList(0)) { 62 | Assert.True(list.Capacity == 0); 63 | Assert.True(list.Count == 0); 64 | Assert.True(list.Ptr == IntPtr.Zero); 65 | } 66 | 67 | // ctor of initializing list from T[] 68 | using(var list = new UnmanagedList(new T[10])) { 69 | Assert.True(list.Capacity == 10); 70 | Assert.True(list.Count == 10); 71 | Assert.True(list.Ptr != IntPtr.Zero); 72 | } 73 | 74 | // ctor of initializing list from ReadOnlySpan 75 | using(var list = new UnmanagedList(new T[10].AsSpan())) { 76 | Assert.True(list.Capacity == 10); 77 | Assert.True(list.Count == 10); 78 | Assert.True(list.Ptr != IntPtr.Zero); 79 | } 80 | 81 | // ctor of initializing list from empty T[] 82 | using(var list = new UnmanagedList(new T[0])) { 83 | Assert.True(list.Capacity == 0); 84 | Assert.True(list.Count == 0); 85 | Assert.True(list.Ptr == IntPtr.Zero); 86 | } 87 | 88 | // ctor of initializing list from empty ReadOnlySpan 89 | using(var list = new UnmanagedList(ReadOnlySpan.Empty)) { 90 | Assert.True(list.Capacity == 0); 91 | Assert.True(list.Count == 0); 92 | Assert.True(list.Ptr == IntPtr.Zero); 93 | } 94 | 95 | // ctor of initializing list from IEnumerable 96 | using(var list = new UnmanagedList(Enumerable.Repeat(default(T), 10))) { 97 | Assert.True(list.Count == 10); 98 | Assert.True(list.Capacity >= 0); 99 | Assert.True(list.Ptr != IntPtr.Zero); 100 | } 101 | 102 | Assert.Throws(() => new UnmanagedList((T[])null!)); 103 | 104 | // Negative value capacity throws an exception. 105 | Assert.Throws(() => new UnmanagedList(-1)); 106 | } 107 | 108 | Ctor_(); 109 | Ctor_(); 110 | Ctor_(); 111 | Ctor_(); 112 | Ctor_(); 113 | Ctor_(); 114 | Ctor_(); 115 | Ctor_(); 116 | Ctor_(); 117 | Ctor_(); 118 | Ctor_(); 119 | Ctor_(); 120 | Ctor_(); 121 | Ctor_(); 122 | } 123 | 124 | [Fact] 125 | public void Ptr() 126 | { 127 | var list = new UnmanagedList(); 128 | 129 | // Ptr is IntPtr.Zero when Count == 0 130 | Assert.True(list.Count == 0); 131 | Assert.Equal(IntPtr.Zero, list.Ptr); 132 | 133 | for(int i = 0; i < 10; i++) { 134 | list.Add(i); 135 | Assert.NotEqual(IntPtr.Zero, list.Ptr); 136 | } 137 | 138 | unsafe { 139 | var ptr = (int*)list.Ptr; 140 | for(int i = 0; i < 10; i++) { 141 | Assert.Equal(i, ptr[i]); 142 | } 143 | } 144 | 145 | list.Dispose(); 146 | 147 | // Ptr is IntPtr.Zero after Dispose() 148 | Assert.True(list.Count == 0); 149 | Assert.Equal(IntPtr.Zero, list.Ptr); 150 | } 151 | 152 | [Fact] 153 | public void Capacity() 154 | { 155 | using(var list = new UnmanagedList(10)) { 156 | Assert.Equal(10, list.Capacity); 157 | Assert.True(list.Count == 0); 158 | 159 | list.Capacity = 20; 160 | Assert.Equal(20, list.Capacity); 161 | Assert.True(list.Count == 0); 162 | 163 | list.Capacity = 0; 164 | Assert.Equal(0, list.Capacity); 165 | Assert.True(list.Count == 0); 166 | } 167 | 168 | using(var list = new UnmanagedList(Enumerable.Range(0, 10))) { 169 | Assert.Equal(10, list.Count); 170 | Assert.True(list.Capacity >= 10); 171 | for(int i = 0; i < 10; i++) { 172 | Assert.Equal(i, list[i]); 173 | } 174 | 175 | list.Capacity = 20; 176 | 177 | Assert.Equal(10, list.Count); 178 | Assert.Equal(20, list.Capacity); 179 | for(int i = 0; i < 10; i++) { 180 | Assert.Equal(i, list[i]); 181 | } 182 | } 183 | 184 | using(var list = new UnmanagedList(10)) { 185 | Assert.True(list.Count == 0); 186 | Assert.Equal(10, list.Capacity); 187 | 188 | list.Capacity = 0; 189 | Assert.True(list.Count == 0); 190 | Assert.True(list.Capacity == 0); 191 | } 192 | } 193 | 194 | [Fact] 195 | public void Indexer() 196 | { 197 | using(var list = new UnmanagedList()) { 198 | Assert.Throws(() => list[0] = default); 199 | Assert.Throws(() => list[1] = default); 200 | Assert.Throws(() => list[-1] = default); 201 | } 202 | 203 | var items = Enumerable.Range(0, 10).ToArray(); 204 | using(var list = new UnmanagedList(items.AsSpan())) { 205 | Assert.Throws(() => list[-1] = default); 206 | Assert.Throws(() => list[10] = default); 207 | Assert.Throws(() => list[20] = default); 208 | 209 | for(int i = 0; i < list.Count; i++) { 210 | Assert.Equal(i, list[i]); 211 | } 212 | 213 | var firstItem = list[0]; 214 | for(int i = 0; i < list.Count - 1; i++) { 215 | list[i] = list[i + 1]; 216 | } 217 | list[list.Count - 1] = firstItem; 218 | 219 | for(int i = 0; i < list.Count; i++) { 220 | Assert.Equal((i + 1) % list.Count, list[i]); 221 | } 222 | } 223 | } 224 | 225 | [Fact] 226 | public void GetReference() 227 | { 228 | ref var nullRef = ref Unsafe.AsRef((void*)null); 229 | 230 | using(var list = new UnmanagedList()) { 231 | 232 | // GetReference() throws no exceptions when list is empty. 233 | // It returns reference to null. 234 | Assert.True(Unsafe.AreSame(ref list.GetReference(), ref nullRef)); 235 | 236 | // GetReference(int) throws ArgumentOutOfRangeException 237 | Assert.Throws(() => list.GetReference(0)); 238 | 239 | list.Add(10); 240 | Assert.True(list.Count == 1); 241 | Assert.Equal(10, list.GetReference()); 242 | Assert.Equal(10, list.GetReference(0)); 243 | 244 | list.GetReference() = 20; 245 | Assert.Equal(20, list[0]); 246 | 247 | list.GetReference(0) = 0; 248 | Assert.Equal(0, list[0]); 249 | 250 | 251 | list.Add(1); 252 | list.Add(2); 253 | list.Add(3); 254 | list.Add(4); 255 | Assert.True(list.Count == 5); 256 | Assert.Equal(0, list.GetReference()); 257 | for(int i = 0; i < list.Count; i++) { 258 | Assert.Equal(i, list.GetReference(i)); 259 | } 260 | Assert.Throws(() => list.GetReference(-1)); 261 | Assert.Throws(() => list.GetReference(5)); 262 | Assert.Throws(() => list.GetReference(10)); 263 | } 264 | } 265 | 266 | [Fact] 267 | public void ListDispose() 268 | { 269 | static void ListDispose_() where T : unmanaged 270 | { 271 | var list = new UnmanagedList(new T[5].AsSpan()); 272 | Assert.True(list.Count == 5); 273 | Assert.True(list.Capacity >= 5); 274 | Assert.True(list.Ptr != IntPtr.Zero); 275 | list.Dispose(); 276 | 277 | // After disposing, those properties may be cleared. 278 | Assert.True(list.Ptr == IntPtr.Zero); 279 | Assert.True(list.Count == 0); 280 | Assert.True(list.Capacity == 0); 281 | 282 | // No exceptions would be thown on re-disposing. 283 | list.Dispose(); 284 | } 285 | 286 | ListDispose_(); 287 | ListDispose_(); 288 | ListDispose_(); 289 | } 290 | 291 | [Fact] 292 | public void Add() 293 | { 294 | // Case of default capacity, of type int 295 | using(var list = new UnmanagedList()) { 296 | var len = 100; 297 | for(int i = 0; i < len; i++) { 298 | list.Add(i); 299 | Assert.True(list.Count == i + 1); 300 | Assert.True(list[i] == i); 301 | } 302 | 303 | Assert.True(list.Count == len); 304 | Assert.True(list.Capacity >= list.Count); 305 | } 306 | 307 | // Case of default capacity, of type long 308 | using(var list = new UnmanagedList()) { 309 | var len = 100; 310 | for(int i = 0; i < len; i++) { 311 | list.Add(i); 312 | Assert.True(list.Count == i + 1); 313 | Assert.True(list[i] == i); 314 | } 315 | 316 | Assert.True(list.Count == len); 317 | Assert.True(list.Capacity >= list.Count); 318 | } 319 | 320 | // Case of default capacity, of type bool 321 | using(var list = new UnmanagedList()) { 322 | var len = 100; 323 | for(int i = 0; i < len; i++) { 324 | var value = i % 3 == 0; 325 | list.Add(value); 326 | Assert.True(list.Count == i + 1); 327 | Assert.True(list[i] == value); 328 | } 329 | 330 | Assert.True(list.Count == len); 331 | Assert.True(list.Capacity >= list.Count); 332 | } 333 | 334 | 335 | 336 | // Case of empty capacity, of type int 337 | using(var list = new UnmanagedList(0)) { 338 | var len = 20; 339 | for(int i = 0; i < len; i++) { 340 | list.Add(i); 341 | Assert.True(list.Count == i + 1); 342 | Assert.True(list[i] == i); 343 | } 344 | 345 | Assert.True(list.Count == len); 346 | Assert.True(list.Capacity >= list.Count); 347 | } 348 | 349 | // Case of specified capacity, of type bool 350 | using(var list = new UnmanagedList(10)) { 351 | var len = 100; 352 | for(int i = 0; i < len; i++) { 353 | var value = i % 3 == 0; 354 | list.Add(value); 355 | Assert.True(list.Count == i + 1); 356 | Assert.True(list[i] == value); 357 | } 358 | 359 | Assert.True(list.Count == len); 360 | Assert.True(list.Capacity >= list.Count); 361 | } 362 | } 363 | 364 | [Fact] 365 | public void AddRange() 366 | { 367 | var itemsCount = 10; 368 | var itemsEnumerable = Enumerable.Range(0, itemsCount); 369 | var items = itemsEnumerable.ToArray(); 370 | 371 | static void TestForSpan(ReadOnlySpan span) 372 | { 373 | using(var list = new UnmanagedList()) { 374 | var len = 10; 375 | for(int i = 0; i < len; i++) { 376 | list.AddRange(span); 377 | Assert.True(list.Count == span.Length * (i + 1)); 378 | Assert.True(list.Capacity >= list.Count); 379 | } 380 | for(int i = 0; i < list.Count; i++) { 381 | Assert.True(list[i] == (i % len)); 382 | } 383 | } 384 | } 385 | 386 | static void TestForIEnumerable(IEnumerable ienumerable, int count) 387 | { 388 | using(var list = new UnmanagedList()) { 389 | var len = 10; 390 | for(int i = 0; i < len; i++) { 391 | list.AddRange(ienumerable); 392 | Assert.True(list.Count == count * (i + 1)); 393 | Assert.True(list.Capacity >= list.Count); 394 | } 395 | for(int i = 0; i < list.Count; i++) { 396 | Assert.True(list[i] == (i % len)); 397 | } 398 | } 399 | } 400 | 401 | static void TestForSelf_AsSpan(ReadOnlySpan initialItems) 402 | { 403 | using(var list = new UnmanagedList(initialItems)) { 404 | var len = 10; 405 | var count = initialItems.Length; 406 | for(int i = 0; i < len; i++) { 407 | list.AddRange(list.AsSpan()); 408 | count += count; 409 | Assert.True(list.Count == count); 410 | Assert.True(list.Capacity >= list.Count); 411 | } 412 | for(int i = 0; i < list.Count; i++) { 413 | Assert.True(list[i] == (i % len)); 414 | } 415 | } 416 | } 417 | 418 | static void TestForSelf_AsIEnumerable(ReadOnlySpan initialItems) 419 | { 420 | using(var list = new UnmanagedList(initialItems)) { 421 | var len = 10; 422 | var count = initialItems.Length; 423 | for(int i = 0; i < len; i++) { 424 | list.AddRange(list.AsEnumerable()); 425 | count += count; 426 | Assert.True(list.Count == count); 427 | Assert.True(list.Capacity >= list.Count); 428 | } 429 | for(int i = 0; i < list.Count; i++) { 430 | Assert.True(list[i] == (i % len)); 431 | } 432 | } 433 | } 434 | 435 | // AddRange T[] as ReadOnlySpan 436 | TestForSpan(items); 437 | 438 | // AddRange T[] as IEnumerable 439 | TestForIEnumerable(items, items.Length); 440 | 441 | // AddRange List as IEnumerable 442 | TestForIEnumerable(items.ToList(), items.Length); 443 | 444 | // AddRange ReadOnlyCollection as IEnumerable 445 | TestForIEnumerable(items.ToList().AsReadOnly(), items.Length); 446 | 447 | // AddRange IEnumerable 448 | TestForIEnumerable(itemsEnumerable, itemsCount); 449 | 450 | // AddRange self as ReadOnlySpan 451 | TestForSelf_AsSpan(items.AsSpan()); 452 | 453 | // AddRange self as IEnumerable 454 | TestForSelf_AsIEnumerable(items.AsSpan()); 455 | } 456 | 457 | [Fact] 458 | public void Extend() 459 | { 460 | foreach(var zeroFill in stackalloc[] { true, false }) { 461 | using(var list = new UnmanagedList(0)) { 462 | TestExtend(list, 10, zeroFill); 463 | } 464 | using(var list = new UnmanagedList(10)) { 465 | TestExtend(list, 5, zeroFill); 466 | TestExtend(list, 5, zeroFill); 467 | TestExtend(list, 5, zeroFill); 468 | } 469 | using(var list = new UnmanagedList(30)) { 470 | TestExtend(list, 50, zeroFill); 471 | } 472 | } 473 | 474 | static void TestExtend(UnmanagedList list, int exCount, bool zeroFill) 475 | { 476 | var count = list.Count; 477 | var span = list.Extend(exCount, zeroFill); 478 | Assert.True(list.Capacity >= exCount + count); 479 | Assert.True(list.Count == exCount + count); 480 | Assert.True(span.Length == exCount); 481 | if(zeroFill) { 482 | foreach(var item in span) { 483 | Assert.True(item == default); 484 | } 485 | } 486 | for(int i = 0; i < span.Length; i++) { 487 | span[i] = i; 488 | } 489 | for(int i = 0; i < span.Length; i++) { 490 | Assert.True(span[i] == list[count + i]); 491 | } 492 | } 493 | } 494 | 495 | [Fact] 496 | public void CopyTo() 497 | { 498 | var items = Enumerable.Range(0, 100).ToArray(); 499 | var boolItems = items.Select(x => x % 3 == 0).ToArray(); 500 | 501 | // basic copy to array 502 | using(var list = new UnmanagedList(items.AsSpan())) { 503 | var copy = new int[items.Length]; 504 | list.CopyTo(copy, 0); 505 | for(int i = 0; i < list.Count; i++) { 506 | Assert.Equal(copy[i], list[i]); 507 | } 508 | } 509 | 510 | // copy to array with offset 511 | using(var list = new UnmanagedList(boolItems.AsSpan())) { 512 | var offset = 13; 513 | var copy = new bool[boolItems.Length + offset]; 514 | list.CopyTo(copy, offset); 515 | for(int i = 0; i < list.Count; i++) { 516 | Assert.Equal(copy[i + offset], list[i]); 517 | } 518 | } 519 | 520 | // in case of empty list 521 | using(var list = new UnmanagedList()) { 522 | var copy = new double[0]; 523 | 524 | // no exceptions thrown 525 | list.CopyTo(copy, 0); 526 | } 527 | } 528 | 529 | [Fact] 530 | public void AsSpan() 531 | { 532 | using(var list = new UnmanagedList()) { 533 | Assert.True(list.AsSpan().IsEmpty); 534 | } 535 | 536 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 537 | var span = list.AsSpan(); 538 | for(int i = 0; i < span.Length; i++) { 539 | Assert.Equal(span[i], list[i]); 540 | Assert.True(Unsafe.AreSame(ref span[i], ref list.GetReference(i))); 541 | } 542 | } 543 | 544 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 545 | var span = list.AsSpan(20); 546 | for(int i = 0; i < span.Length - 20; i++) { 547 | Assert.Equal(span[i], list[i + 20]); 548 | Assert.True(Unsafe.AreSame(ref span[i], ref list.GetReference(i + 20))); 549 | } 550 | } 551 | 552 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 553 | var span = list.AsSpan(20, 80); 554 | for(int i = 0; i < 80; i++) { 555 | Assert.Equal(span[i], list[i + 20]); 556 | Assert.True(Unsafe.AreSame(ref span[i], ref list.GetReference(i + 20))); 557 | } 558 | 559 | Assert.True(list.AsSpan(20, 0).IsEmpty); 560 | } 561 | 562 | using(var list = new UnmanagedList(Enumerable.Range(0, 10).ToArray().AsSpan())) { 563 | Assert.True(list.Count == 10); 564 | var span = list.AsSpan(); 565 | Assert.True(list.AsSpan(10).IsEmpty); 566 | Assert.True(list.AsSpan(10, 0).IsEmpty); 567 | Assert.True(list.AsSpan(0).SequenceEqual(span)); 568 | Assert.True(list.AsSpan(0, 10).SequenceEqual(span)); 569 | } 570 | 571 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 572 | Assert.Throws(() => list.AsSpan(-1)); 573 | Assert.Throws(() => list.AsSpan(20, 81)); 574 | Assert.Throws(() => list.AsSpan(20, -1)); 575 | Assert.Throws(() => list.AsSpan(-1, 100)); 576 | } 577 | 578 | { 579 | var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan()); 580 | list.Dispose(); 581 | // AsSpan() throws no exceptions after Dispose(). 582 | Assert.True(list.AsSpan().IsEmpty); 583 | } 584 | } 585 | 586 | [Fact] 587 | public void IndexOf() 588 | { 589 | using(var list = new UnmanagedList(Enumerable.Range(10, 100).ToArray().AsSpan())) { 590 | for(int i = 0; i < list.Count; i++) { 591 | Assert.Equal(i, list.IndexOf(i + 10)); 592 | } 593 | Assert.Equal(-1, list.IndexOf(3)); 594 | Assert.Equal(-1, list.IndexOf(200)); 595 | 596 | list.Clear(); 597 | 598 | for(int i = 0; i < 100; i++) { 599 | Assert.Equal(-1, list.IndexOf(i + 10)); 600 | } 601 | } 602 | } 603 | 604 | [Fact] 605 | public void RemoveAt() 606 | { 607 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 608 | for(int i = 0; i < 100; i++) { 609 | Assert.Equal(i, list[0]); 610 | list.RemoveAt(0); 611 | Assert.Equal(100 - i - 1, list.Count); 612 | } 613 | 614 | // list is empty 615 | 616 | Assert.Throws(() => list.RemoveAt(0)); 617 | Assert.Throws(() => list.RemoveAt(-1)); 618 | Assert.Throws(() => list.RemoveAt(1)); 619 | } 620 | 621 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 622 | Assert.Throws(() => list.RemoveAt(-1)); 623 | Assert.Throws(() => list.RemoveAt(100)); 624 | 625 | Assert.Equal(100, list.Count); 626 | Assert.True(list.Capacity >= list.Count); 627 | 628 | for(int i = 0; i < 30; i++) { 629 | Assert.Equal(70 + i, list[70]); 630 | list.RemoveAt(70); 631 | Assert.Equal(100 - i - 1, list.Count); 632 | } 633 | 634 | for(int i = 0; i < list.Count; i++) { 635 | Assert.Equal(i, list[i]); 636 | } 637 | } 638 | } 639 | 640 | [Fact] 641 | public void Clear() 642 | { 643 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 644 | var capacity = list.Capacity; 645 | Assert.True(list.Count == 100); 646 | list.Clear(); 647 | Assert.True(list.Count == 0); 648 | Assert.Equal(capacity, list.Capacity); 649 | } 650 | } 651 | 652 | [Fact] 653 | public void Contains() 654 | { 655 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 656 | Assert.Equal(100, list.Count); 657 | for(int i = 0; i < list.Count; i++) { 658 | Assert.True(list.Contains(i) == true); 659 | } 660 | 661 | Assert.True(list.Contains(-1) == false); 662 | Assert.True(list.Contains(-10) == false); 663 | Assert.True(list.Contains(100) == false); 664 | Assert.True(list.Contains(1000) == false); 665 | 666 | list.Clear(); 667 | 668 | for(int i = 0; i < 100; i++) { 669 | Assert.True(list.Contains(i) == false); 670 | } 671 | } 672 | } 673 | 674 | [Fact] 675 | public void Remove() 676 | { 677 | using(var list = new UnmanagedList()) { 678 | list.Add(5); 679 | Assert.True(list.Count == 1); 680 | Assert.True(list[0] == 5); 681 | 682 | Assert.True(list.Remove(5)); 683 | Assert.True(list.Count == 0); 684 | } 685 | 686 | using(var list = new UnmanagedList()) { 687 | for(int i = 0; i < 50; i++) { 688 | list.Add(i); 689 | } 690 | for(int i = 0; i < 50; i++) { 691 | Assert.True(list.Remove(i)); 692 | Assert.True(list.Count == 50 - i - 1); 693 | } 694 | } 695 | 696 | using(var list = new UnmanagedList()) { 697 | list.Add(0); 698 | list.Add(1); 699 | list.Add(2); 700 | list.Add(3); 701 | list.Add(4); 702 | list.Add(5); 703 | Assert.True(list.Count == 6); 704 | 705 | Assert.True(list.Remove(4)); 706 | Assert.True(list.Count == 5); 707 | Assert.True(list[0] == 0); 708 | Assert.True(list[1] == 1); 709 | Assert.True(list[2] == 2); 710 | Assert.True(list[3] == 3); 711 | Assert.True(list[4] == 5); 712 | 713 | Assert.True(list.Remove(2)); 714 | Assert.True(list.Count == 4); 715 | Assert.True(list[0] == 0); 716 | Assert.True(list[1] == 1); 717 | Assert.True(list[2] == 3); 718 | Assert.True(list[3] == 5); 719 | 720 | // false if not exist 721 | Assert.False(list.Remove(2)); 722 | 723 | Assert.True(list.Remove(5)); 724 | Assert.True(list.Count == 3); 725 | Assert.True(list[0] == 0); 726 | Assert.True(list[1] == 1); 727 | Assert.True(list[2] == 3); 728 | 729 | Assert.True(list.Remove(0)); 730 | Assert.True(list.Count == 2); 731 | Assert.True(list[0] == 1); 732 | Assert.True(list[1] == 3); 733 | 734 | Assert.True(list.Remove(1)); 735 | Assert.True(list.Count == 1); 736 | Assert.True(list[0] == 3); 737 | 738 | Assert.True(list.Remove(3)); 739 | Assert.True(list.Count == 0); 740 | 741 | // false if not exist 742 | Assert.False(list.Remove(5)); 743 | } 744 | } 745 | 746 | [Fact] 747 | public void Enumerate() 748 | { 749 | // Iterate in three ways 750 | using(var list = new UnmanagedList(Enumerable.Range(0, 100).ToArray().AsSpan())) { 751 | 752 | // Iterate by foreach 753 | { 754 | var i = 0; 755 | foreach(var item in list) { 756 | Assert.Equal(i, item); 757 | i++; 758 | } 759 | } 760 | 761 | // Iterate by foreach as IEnumerable 762 | { 763 | var i = 0; 764 | foreach(var item in list.AsEnumerable()) { 765 | Assert.Equal(i, item); 766 | i++; 767 | } 768 | } 769 | 770 | // Iterate by foreach as IEnumerable 771 | { 772 | var i = 0; 773 | foreach(var item in (System.Collections.IEnumerable)list) { 774 | Assert.Equal(i, (int)item!); 775 | i++; 776 | } 777 | } 778 | } 779 | 780 | 781 | // Iterate empty list in three ways 782 | using(var list = new UnmanagedList()) { 783 | Assert.True(list.Count == 0); 784 | 785 | // Iterate by foreach 786 | foreach(var item in list) { 787 | throw new Exception("invalid"); 788 | } 789 | 790 | // Iterate by foreach as IEnumerable 791 | foreach(var item in list.AsEnumerable()) { 792 | throw new Exception("invalid"); 793 | } 794 | 795 | // Iterate by foreach as IEnumerable 796 | foreach(var item in (System.Collections.IEnumerable)list) { 797 | throw new Exception("invalid"); 798 | } 799 | } 800 | } 801 | 802 | [Fact] 803 | public void Insert() 804 | { 805 | using(var list = new UnmanagedList()) { 806 | list.Insert(0, 1); 807 | list.Insert(0, 2); 808 | list.Insert(0, 3); 809 | list.Insert(0, 4); 810 | list.Insert(2, 5); 811 | list.Insert(5, 6); 812 | 813 | // list : [4, 3, 5, 2, 1, 6] 814 | Assert.Equal(6, list.Count); 815 | Assert.Equal(4, list[0]); 816 | Assert.Equal(3, list[1]); 817 | Assert.Equal(5, list[2]); 818 | Assert.Equal(2, list[3]); 819 | Assert.Equal(1, list[4]); 820 | Assert.Equal(6, list[5]); 821 | 822 | } 823 | } 824 | 825 | [Fact] 826 | public void InsertRange() 827 | { 828 | var initial = Enumerable.Range(10, 20).ToArray(); 829 | 830 | var itemsCount = 10; 831 | var itemsEnumerable = Enumerable.Range(0, itemsCount); 832 | var items = itemsEnumerable.ToArray(); 833 | 834 | { 835 | using(var list = new UnmanagedList(initial)) { 836 | var insert = 0; 837 | list.InsertRange(insert, items); 838 | Assert.True(list.Count == initial.Length + items.Length); 839 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 840 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 841 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 842 | } 843 | 844 | using(var list = new UnmanagedList(initial)) { 845 | var insert = 5; 846 | list.InsertRange(insert, items); 847 | Assert.True(list.Count == initial.Length + items.Length); 848 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 849 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 850 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 851 | } 852 | 853 | using(var list = new UnmanagedList(initial)) { 854 | var insert = initial.Length; 855 | list.InsertRange(insert, items); 856 | Assert.True(list.Count == initial.Length + items.Length); 857 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 858 | Assert.True(list.AsSpan(insert).SequenceEqual(items)); 859 | } 860 | } 861 | 862 | { 863 | using(var list = new UnmanagedList(initial)) { 864 | var insert = 0; 865 | list.InsertRange(insert, itemsEnumerable); 866 | Assert.True(list.Count == initial.Length + items.Length); 867 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 868 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 869 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 870 | } 871 | 872 | using(var list = new UnmanagedList(initial)) { 873 | var insert = 5; 874 | list.InsertRange(insert, itemsEnumerable); 875 | Assert.True(list.Count == initial.Length + items.Length); 876 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 877 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 878 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 879 | } 880 | 881 | using(var list = new UnmanagedList(initial)) { 882 | var insert = initial.Length; 883 | list.InsertRange(insert, itemsEnumerable); 884 | Assert.True(list.Count == initial.Length + items.Length); 885 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 886 | Assert.True(list.AsSpan(insert).SequenceEqual(items)); 887 | } 888 | } 889 | 890 | { 891 | using(var list = new UnmanagedList(initial)) { 892 | var insert = 0; 893 | list.InsertRange(insert, items.AsSpan()); 894 | Assert.True(list.Count == initial.Length + items.Length); 895 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 896 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 897 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 898 | } 899 | 900 | using(var list = new UnmanagedList(initial)) { 901 | var insert = 5; 902 | list.InsertRange(insert, items.AsSpan()); 903 | Assert.True(list.Count == initial.Length + items.Length); 904 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 905 | Assert.True(list.AsSpan(insert, items.Length).SequenceEqual(items)); 906 | Assert.True(list.AsSpan(insert + items.Length).SequenceEqual(initial.AsSpan(insert))); 907 | } 908 | 909 | using(var list = new UnmanagedList(initial)) { 910 | var insert = initial.Length; 911 | list.InsertRange(insert, items.AsSpan()); 912 | Assert.True(list.Count == initial.Length + items.Length); 913 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 914 | Assert.True(list.AsSpan(insert).SequenceEqual(items)); 915 | } 916 | } 917 | 918 | // Test for inserting to self 919 | { 920 | using(var list = new UnmanagedList(initial)) { 921 | var c = list.Count; 922 | list.InsertRange(0, list.AsSpan()); 923 | Assert.True(list.Count == initial.Length * 2); 924 | Assert.True(list.AsSpan(0, c).SequenceEqual(list.AsSpan(c, c))); 925 | } 926 | 927 | using(var list = new UnmanagedList(initial)) { 928 | var copy = list.AsSpan(0, 10).ToArray(); 929 | var insert = 5; 930 | list.InsertRange(insert, list.AsSpan(0, 10)); 931 | Assert.True(list.Count == initial.Length + copy.Length); 932 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 933 | Assert.True(list.AsSpan(insert, copy.Length).SequenceEqual(copy)); 934 | Assert.True(list.AsSpan(insert + copy.Length).SequenceEqual(initial.AsSpan(insert))); 935 | } 936 | 937 | 938 | 939 | using(var list = new UnmanagedList(initial.Length * 2 + 10)) { 940 | list.AddRange(initial); 941 | 942 | var span = list.AsSpan(8, 10); 943 | var copy = span.ToArray(); 944 | var insert = 5; 945 | list.InsertRange(insert, span); 946 | Assert.True(list.Count == initial.Length + copy.Length); 947 | 948 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 949 | Assert.True(list.AsSpan(insert, copy.Length).SequenceEqual(copy)); 950 | Assert.True(list.AsSpan(insert + copy.Length).SequenceEqual(initial.AsSpan(insert))); 951 | } 952 | 953 | using(var list = new UnmanagedList(initial.Length * 2 + 10)) { 954 | list.AddRange(initial); 955 | 956 | var span = list.AsSpan(3, 10); 957 | var copy = span.ToArray(); 958 | var insert = 15; 959 | list.InsertRange(insert, span); 960 | Assert.True(list.Count == initial.Length + copy.Length); 961 | 962 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 963 | Assert.True(list.AsSpan(insert, copy.Length).SequenceEqual(copy)); 964 | Assert.True(list.AsSpan(insert + copy.Length).SequenceEqual(initial.AsSpan(insert))); 965 | } 966 | 967 | using(var list = new UnmanagedList(initial.Length * 2 + 10)) { 968 | list.AddRange(initial); 969 | 970 | var span = list.AsSpan(3, 10); 971 | var copy = span.ToArray(); 972 | var insert = 6; 973 | list.InsertRange(insert, span); 974 | Assert.True(list.Count == initial.Length + copy.Length); 975 | 976 | Assert.True(list.AsSpan(0, insert).SequenceEqual(initial.AsSpan(0, insert))); 977 | Assert.True(list.AsSpan(insert, copy.Length).SequenceEqual(copy)); 978 | Assert.True(list.AsSpan(insert + copy.Length).SequenceEqual(initial.AsSpan(insert))); 979 | } 980 | 981 | using(var list = new UnmanagedList(initial.Length * 2 + 10)) { 982 | list.AddRange(initial); 983 | 984 | var span = list.AsSpan(3, 0); 985 | var copy = span.ToArray(); 986 | list.InsertRange(6, span); 987 | Assert.True(list.AsSpan().SequenceEqual(initial)); 988 | } 989 | } 990 | } 991 | 992 | [Fact] 993 | public void Linq() 994 | { 995 | var items = Enumerable.Range(0, 10).ToArray(); 996 | 997 | using(var list = Enumerable.Range(0, 10).ToUnmanagedList()) { 998 | Assert.True(list.AsSpan().SequenceEqual(items)); 999 | 1000 | Assert.True( 1001 | list.Where(x => x % 2 == 0) 1002 | .Select(x => x * 3) 1003 | .SequenceEqual( 1004 | items.Where(x => x % 2 == 0) 1005 | .Select(x => x * 3))); 1006 | } 1007 | } 1008 | 1009 | [StructLayout(LayoutKind.Sequential)] 1010 | private struct TestData 1011 | { 1012 | public bool Bool; 1013 | public float Float; 1014 | public double Double; 1015 | public int Int; 1016 | } 1017 | } 1018 | } 1019 | -------------------------------------------------------------------------------- /src/UnmanagedArray.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.452 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnmanagedArray", "UnmanagedArray\UnmanagedArray.csproj", "{52B954A6-92FB-4074-9255-E6E3EAB5C1C0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{E1C14DCC-1BE5-4697-9B08-0B72D15217D7}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {52B954A6-92FB-4074-9255-E6E3EAB5C1C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {52B954A6-92FB-4074-9255-E6E3EAB5C1C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {52B954A6-92FB-4074-9255-E6E3EAB5C1C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {52B954A6-92FB-4074-9255-E6E3EAB5C1C0}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {E1C14DCC-1BE5-4697-9B08-0B72D15217D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E1C14DCC-1BE5-4697-9B08-0B72D15217D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E1C14DCC-1BE5-4697-9B08-0B72D15217D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E1C14DCC-1BE5-4697-9B08-0B72D15217D7}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {7F451E0E-0697-4BCF-9A5C-3FE7C9FD2FBC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/UnmanagedArray/ThrowHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 ikorin24 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #nullable enable 27 | using System; 28 | 29 | namespace UnmanageUtility 30 | { 31 | internal static class ThrowHelper 32 | { 33 | public static void ArgumentOutOfRange(string paramName) => throw new ArgumentOutOfRangeException(paramName); 34 | 35 | public static void ArgumentNull(string paramName) => throw new ArgumentNullException(paramName); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/UnmanagedArray/UnmanagedArray.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 ikorin24 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #nullable enable 27 | 28 | #if NET5_0 || NETCOREAPP3_1 29 | #define FAST_SPAN 30 | #endif 31 | 32 | using System; 33 | using System.Collections; 34 | using System.Collections.Generic; 35 | using System.Diagnostics; 36 | using System.Runtime.InteropServices; 37 | using System.Runtime.CompilerServices; 38 | using System.ComponentModel; 39 | 40 | namespace UnmanageUtility 41 | { 42 | /// 43 | /// Array class which is allocated in unmanaged memory. 44 | /// Only for unmanaged types. (e.g. int, float, recursive-unmanaged struct, and so on.) 45 | /// 46 | /// type of array 47 | [DebuggerTypeProxy(typeof(UnmanagedArrayDebuggerTypeProxy<>))] 48 | [DebuggerDisplay("UnmanagedArray<{typeof(T).Name}>[{_length}]")] 49 | public sealed class UnmanagedArray : IList, IReadOnlyList, IList, IReadOnlyCollection, IDisposable 50 | where T : unmanaged 51 | { 52 | private static UnmanagedArray _empty = new UnmanagedArray(0); 53 | 54 | [ThreadStatic] 55 | internal static UnmanagedList? _helperList; 56 | 57 | 58 | internal int _length; 59 | internal IntPtr _array; 60 | 61 | /// Get empty array 62 | public static UnmanagedArray Empty => _empty; 63 | 64 | /// Get pointer address of this array. 65 | public IntPtr Ptr => _array; 66 | 67 | /// Get is disposed. 68 | public bool IsDisposed => _array == IntPtr.Zero; 69 | 70 | /// Get the specific item of specific index. 71 | /// index 72 | /// The item of specific index 73 | public T this[int i] 74 | { 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | get => GetReference(i); 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | set => GetReference(i) = value; 79 | } 80 | 81 | /// Get length of this array 82 | public int Length => _length; // No checking disposed because this property is safe. (for performance of "for(int i = 0; i < unmanagedArray.Length; i++)") 83 | 84 | 85 | // *** NOTICE *** 86 | // T[] a = new T[10]; 87 | // (a as ICollection).IsReadOnly ----> true 88 | // (a as IList).IsReadOnly ----> false 89 | // 90 | // ↓ I copied thier values of the properties. 91 | 92 | bool ICollection.IsReadOnly => true; 93 | 94 | bool IList.IsReadOnly => false; 95 | 96 | bool IList.IsFixedSize => true; 97 | 98 | int ICollection.Count => _length; // No checking disposed because this property is safe. 99 | 100 | int IReadOnlyCollection.Count => _length; // No checking disposed because this property is safe. 101 | 102 | int ICollection.Count => _length; // No checking disposed because this property is safe. 103 | 104 | object ICollection.SyncRoot => this; 105 | 106 | bool ICollection.IsSynchronized => false; 107 | 108 | #pragma warning disable CS8769 109 | object IList.this[int index] { get => this[index]; set => this[index] = (T)value; } 110 | #pragma warning restore CS8769 111 | 112 | /// UnmanagedArray Constructor 113 | /// Length of array 114 | public unsafe UnmanagedArray(int length) : this(length, default) { } 115 | 116 | /// Create new filled by specified element. 117 | /// length of array 118 | /// element that fills array 119 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 120 | public unsafe UnmanagedArray(int length, T fill) 121 | { 122 | if(length < 0) { 123 | ThrowOutOfRange(); 124 | static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(); 125 | } 126 | var bytes = sizeof(T) * length; 127 | if(bytes == 0) { return; } 128 | _array = Marshal.AllocHGlobal(bytes); 129 | _length = length; 130 | new Span((void*)_array, _length).Fill(fill); 131 | } 132 | 133 | private unsafe UnmanagedArray() 134 | { 135 | } 136 | 137 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 138 | internal unsafe static UnmanagedArray CreateWithoutZeroFill(int length) 139 | { 140 | if(length < 0) { 141 | ThrowOutOfRange(); 142 | static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(length)); 143 | } 144 | var umarray = new UnmanagedArray(); 145 | var bytes = length * sizeof(T); 146 | if(bytes > 0) { 147 | umarray._array = Marshal.AllocHGlobal(bytes); 148 | umarray._length = length; 149 | } 150 | return umarray; 151 | } 152 | 153 | /// Create new , those elements are copied from . 154 | /// Elements of the are initialized by this . 155 | public unsafe UnmanagedArray(ReadOnlySpan span) 156 | { 157 | var bytes = span.Length * sizeof(T); 158 | if(bytes == 0) { return; } 159 | _array = Marshal.AllocHGlobal(bytes); 160 | _length = span.Length; 161 | span.CopyTo(new Span((void*)_array, _length)); 162 | } 163 | 164 | /// Finalizer of 165 | ~UnmanagedArray() => Dispose(false); 166 | 167 | /// Get reference to head item (Returns ref to null if empty) 168 | /// reference to head item 169 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 170 | public unsafe ref T GetReference() 171 | { 172 | #if DEBUG 173 | if(_length == 0) { 174 | Debug.Assert(_array == IntPtr.Zero); 175 | } 176 | #endif 177 | return ref Unsafe.AsRef((T*)_array); 178 | } 179 | 180 | /// Get reference to head item (Returns ref to null if empty) 181 | /// reference to head item 182 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 183 | public unsafe ref T GetReference(int index) 184 | { 185 | if((uint)index >= (uint)_length) { 186 | ThrowOutOfRange(); 187 | static void ThrowOutOfRange() => throw new IndexOutOfRangeException(); 188 | } 189 | return ref Unsafe.Add(ref GetReference(), index); 190 | } 191 | 192 | /// Get enumerator instance. 193 | /// 194 | public Enumerator GetEnumerator() 195 | { 196 | ThrowIfDisposed(); 197 | return new Enumerator(this); 198 | } 199 | 200 | IEnumerator IEnumerable.GetEnumerator() 201 | { 202 | ThrowIfDisposed(); 203 | // Avoid boxing by using class enumerator. 204 | return new EnumeratorClass(this); 205 | } 206 | 207 | IEnumerator IEnumerable.GetEnumerator() 208 | { 209 | ThrowIfDisposed(); 210 | // Avoid boxing by using class enumerator. 211 | return new EnumeratorClass(this); 212 | } 213 | 214 | /// Get index of the item 215 | /// target item 216 | /// index (if not contain, value is -1) 217 | public unsafe int IndexOf(T item) 218 | { 219 | ThrowIfDisposed(); 220 | for(int i = 0; i < _length; i++) { 221 | if(EqualityComparer.Default.Equals(((T*)_array)[i], item)) { 222 | return i; 223 | } 224 | } 225 | return -1; 226 | } 227 | 228 | /// Get whether this instance contains the item. 229 | /// target item 230 | /// true: This array contains the target item. false: not contain 231 | public unsafe bool Contains(T item) 232 | { 233 | ThrowIfDisposed(); 234 | for(int i = 0; i < _length; i++) { 235 | if(EqualityComparer.Default.Equals(((T*)_array)[i], item)) { 236 | return true; 237 | } 238 | } 239 | return false; 240 | } 241 | 242 | /// Copy to managed memory 243 | /// managed memory array 244 | /// start index of destination array 245 | public void CopyTo(T[] array, int arrayIndex) 246 | { 247 | ThrowIfDisposed(); 248 | if(array == null) { throw new ArgumentNullException(nameof(array)); } 249 | if((uint)arrayIndex >= (uint)array.Length) { throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } 250 | if(arrayIndex + _length > array.Length) { throw new ArgumentException("There is not enouph length of destination array"); } 251 | unsafe { 252 | var objsize = sizeof(T); 253 | fixed(T* arrayPtr = array) { 254 | var byteLen = (long)(_length * objsize); 255 | var dest = new IntPtr(arrayPtr) + arrayIndex * objsize; 256 | Buffer.MemoryCopy((void*)_array, (void*)dest, byteLen, byteLen); 257 | } 258 | } 259 | } 260 | 261 | void IList.Insert(int index, T item) => throw new NotSupportedException(); 262 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 263 | void ICollection.Add(T item) => throw new NotSupportedException(); 264 | bool ICollection.Remove(T item) => throw new NotSupportedException(); 265 | void ICollection.Clear() => throw new NotSupportedException(); 266 | 267 | /// Get pointer address of specified index. 268 | /// index 269 | /// pointer address 270 | [Obsolete("Use instead 'Ptr + sizeof(T) * index', that is faster.")] 271 | [EditorBrowsable(EditorBrowsableState.Never)] 272 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 273 | public unsafe IntPtr GetPtrIndexOf(int index) 274 | { 275 | ThrowIfDisposed(); 276 | if((uint)index >= (uint)_length) { 277 | ThrowOutOfRange(); 278 | static void ThrowOutOfRange() => throw new IndexOutOfRangeException(); 279 | } 280 | return new IntPtr((T*)_array + index); 281 | } 282 | 283 | /// Copy fron . 284 | /// source array of type 285 | public void CopyFrom(UnmanagedArray array) => CopyFrom(array.Ptr, 0, array.Length); 286 | 287 | /// Copy from to this of index 0. 288 | /// object. 289 | public void CopyFrom(ReadOnlySpan span) => CopyFrom(span, 0); 290 | 291 | /// Copy from to this of specified index. 292 | /// object. 293 | /// start index of destination. (destination is this .) 294 | public unsafe void CopyFrom(ReadOnlySpan source, int start) 295 | { 296 | ThrowIfDisposed(); 297 | if(start < 0) { throw new ArgumentOutOfRangeException(); } 298 | if(start + source.Length > _length) { throw new ArgumentOutOfRangeException(); } 299 | var objsize = sizeof(T); 300 | fixed(T* ptr = source) { 301 | var byteLen = (long)(source.Length * objsize); 302 | Buffer.MemoryCopy(ptr, (void*)(_array + start * objsize), byteLen, byteLen); 303 | } 304 | } 305 | 306 | /// Copy from unmanaged. 307 | /// unmanaged source pointer 308 | /// start index of destination. (destination is this .) 309 | /// count of copied item. (NOT length of bytes.) 310 | public unsafe void CopyFrom(IntPtr source, int start, int length) 311 | { 312 | ThrowIfDisposed(); 313 | if(length == 0) { return; } 314 | if(source == IntPtr.Zero) { throw new ArgumentNullException("source is null"); } 315 | if(start < 0 || length < 0) { throw new ArgumentOutOfRangeException(); } 316 | if(start + length > _length) { throw new ArgumentOutOfRangeException(); } 317 | var objsize = sizeof(T); 318 | var byteLen = (long)(length * objsize); 319 | Buffer.MemoryCopy((void*)source, (void*)(_array + start * objsize), byteLen, byteLen); 320 | } 321 | 322 | /// Return of this . 323 | /// 324 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 325 | public unsafe Span AsSpan() 326 | { 327 | #if DEBUG 328 | if(_length == 0) { 329 | Debug.Assert(_array == IntPtr.Zero); 330 | } 331 | #endif 332 | #if FAST_SPAN 333 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef((T*)_array), _length); 334 | #else 335 | return new Span((T*)_array, _length); 336 | #endif 337 | } 338 | 339 | /// Return starts with specified index. 340 | /// start index 341 | /// 342 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 343 | public unsafe Span AsSpan(int start) 344 | { 345 | #if DEBUG 346 | if(_length == 0) { 347 | Debug.Assert(_array == IntPtr.Zero); 348 | } 349 | #endif 350 | if((uint)start > (uint)_length) { 351 | ThrowOutOfRange(); 352 | static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(start)); 353 | } 354 | #if FAST_SPAN 355 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef((T*)_array + start), _length - start); 356 | #else 357 | return new Span((T*)_array + start, _length - start); 358 | #endif 359 | } 360 | 361 | /// Return of specified length starts with specified index. 362 | /// start index 363 | /// length of span 364 | /// 365 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 366 | public unsafe Span AsSpan(int start, int length) 367 | { 368 | #if DEBUG 369 | if(_length == 0) { 370 | Debug.Assert(_array == IntPtr.Zero); 371 | } 372 | #endif 373 | if((uint)start > (uint)_length) { 374 | ThrowOutOfRange(); 375 | static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(start)); 376 | } 377 | if((uint)length > (uint)_length - (uint)start) { 378 | ThrowOutOfRange(); 379 | static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(length)); 380 | } 381 | #if FAST_SPAN 382 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef((T*)_array + start), length); 383 | #else 384 | return new Span((T*)_array + start, length); 385 | #endif 386 | } 387 | 388 | /// Create new whose values are initialized by memory layout of specified structure. 389 | /// type of source structure 390 | /// source structure 391 | /// instance of whose values are initialized by 392 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 393 | public static unsafe UnmanagedArray CreateFromStruct(ref TStruct obj) where TStruct : unmanaged 394 | { 395 | var structSize = sizeof(TStruct); 396 | var itemSize = sizeof(T); 397 | var arrayLen = structSize / itemSize + (structSize % itemSize > 0 ? 1 : 0); 398 | var array = new UnmanagedArray(arrayLen); 399 | fixed(TStruct* ptr = &obj) { 400 | Buffer.MemoryCopy(ptr, (void*)array._array, structSize, structSize); 401 | } 402 | return array; 403 | } 404 | 405 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 406 | internal static unsafe UnmanagedArray DirectCreateWithoutCopy(T* ptr, int length) 407 | { 408 | // Be Careful !!!! This method is very unsafe !! 409 | // 'ptr' must be pointer to unmanaged heap memory. 410 | 411 | var array = new UnmanagedArray(); 412 | array._array = (IntPtr)ptr; 413 | array._length = length; 414 | return array; 415 | } 416 | 417 | /// 418 | /// Dispose this instance and release unmanaged memory. 419 | /// If already disposed, do nothing. 420 | /// 421 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 422 | public void Dispose() 423 | { 424 | Dispose(true); 425 | GC.SuppressFinalize(this); 426 | } 427 | 428 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 429 | private unsafe void Dispose(bool disposing) 430 | { 431 | if(_array == IntPtr.Zero) { return; } 432 | Marshal.FreeHGlobal(_array); 433 | Debug.Assert(sizeof(T) * _length > 0); 434 | _array = IntPtr.Zero; 435 | _length = 0; 436 | } 437 | 438 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 439 | private void ThrowIfDisposed() 440 | { 441 | if(IsDisposed) { throw new ObjectDisposedException(nameof(UnmanagedArray), "Memory of array is already free."); } 442 | } 443 | 444 | #pragma warning disable CS8769 445 | int IList.Add(object value) => throw new NotSupportedException(); 446 | void IList.Clear() => throw new NotSupportedException(); 447 | void IList.Insert(int index, object value) => throw new NotSupportedException(); 448 | void IList.Remove(object value) => throw new NotSupportedException(); 449 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 450 | bool IList.Contains(object value) => (value is T v) ? Contains(v) : false; 451 | int IList.IndexOf(object value) => (value is T v) ? IndexOf(v) : -1; 452 | void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index); 453 | #pragma warning restore CS8769 454 | 455 | /// Enumerator of 456 | public unsafe struct Enumerator : IEnumerator, IEnumerator 457 | { 458 | private readonly T* _ptr; 459 | private readonly int _len; 460 | private int _index; 461 | 462 | /// Get current element 463 | public T Current { get; private set; } 464 | 465 | internal Enumerator(UnmanagedArray array) 466 | { 467 | _ptr = (T*)array._array; 468 | _len = array._length; 469 | _index = 0; 470 | Current = default; 471 | } 472 | 473 | /// Dispose of 474 | public void Dispose() { } 475 | 476 | /// Move to next element 477 | /// true if success to move next. false to end. 478 | public bool MoveNext() 479 | { 480 | if((uint)_index < (uint)_len) { 481 | Current = _ptr[_index]; 482 | _index++; 483 | return true; 484 | } 485 | return MoveNextRare(); 486 | } 487 | 488 | private bool MoveNextRare() 489 | { 490 | _index = _len + 1; 491 | Current = default; 492 | return false; 493 | } 494 | 495 | object IEnumerator.Current => Current; 496 | 497 | void IEnumerator.Reset() 498 | { 499 | _index = 0; 500 | Current = default; 501 | } 502 | } 503 | 504 | /// Enumerator of 505 | public unsafe class EnumeratorClass : IEnumerator, IEnumerator 506 | { 507 | private readonly T* _ptr; 508 | private readonly int _len; 509 | private int _index; 510 | 511 | /// Get current element 512 | public T Current { get; private set; } 513 | 514 | internal EnumeratorClass(UnmanagedArray array) 515 | { 516 | _ptr = (T*)array._array; 517 | _len = array._length; 518 | _index = 0; 519 | Current = default; 520 | } 521 | 522 | /// Dispose of 523 | public void Dispose() { } 524 | 525 | /// Move to next element 526 | /// true if success to move next. false to end. 527 | public bool MoveNext() 528 | { 529 | if((uint)_index < (uint)_len) { 530 | Current = _ptr[_index]; 531 | _index++; 532 | return true; 533 | } 534 | return MoveNextRare(); 535 | } 536 | 537 | private bool MoveNextRare() 538 | { 539 | _index = _len + 1; 540 | Current = default; 541 | return false; 542 | } 543 | 544 | object IEnumerator.Current => Current; 545 | 546 | void IEnumerator.Reset() 547 | { 548 | _index = 0; 549 | Current = default; 550 | } 551 | } 552 | } 553 | 554 | /// Define extension methods of 555 | public static class UnmanagedArrayExtension 556 | { 557 | /// Create a new instance of initialized by source. 558 | /// Type of item in array 559 | /// source which initializes new array. 560 | /// instance of 561 | public static unsafe UnmanagedArray ToUnmanagedArray(this IEnumerable source) where T : unmanaged 562 | { 563 | if(source == null) { throw new ArgumentNullException(nameof(source)); } 564 | if(source is T[] managedArray) { 565 | var array = UnmanagedArray.CreateWithoutZeroFill(managedArray.Length); 566 | managedArray.AsSpan().CopyTo(array.AsSpan()); 567 | return array; 568 | } 569 | else { 570 | var helper = (UnmanagedArray._helperList ??= new UnmanagedList()); 571 | helper.AddRange(source); 572 | helper.TransferInnerMemoryOwnership(out var ptr, out _, out var length); 573 | 574 | // Capacity of allocated memory may be larger than length to use, 575 | // but this occurs no problem. 576 | 577 | return UnmanagedArray.DirectCreateWithoutCopy((T*)ptr, length); 578 | } 579 | } 580 | 581 | /// Create a new instance of initialized by source. 582 | /// Type of item in array 583 | /// source which initializes new array. 584 | /// instance of 585 | public static UnmanagedArray ToUnmanagedArray(this ReadOnlySpan source) where T : unmanaged 586 | { 587 | return new UnmanagedArray(source); 588 | } 589 | 590 | /// Create a new instance of initialized by source. 591 | /// Type of item in array 592 | /// source which initializes new array. 593 | /// instance of 594 | public static UnmanagedArray ToUnmanagedArray(this Span source) where T : unmanaged 595 | { 596 | return new UnmanagedArray(source); 597 | } 598 | 599 | /// Create a new instance of initialized by source. 600 | /// Type of item in array 601 | /// source which initializes new array. 602 | /// instance of 603 | public static UnmanagedArray ToUnmanagedArray(this ReadOnlyMemory source) where T : unmanaged 604 | { 605 | return new UnmanagedArray(source.Span); 606 | } 607 | 608 | /// Create a new instance of initialized by source. 609 | /// Type of item in array 610 | /// source which initializes new array. 611 | /// instance of 612 | public static UnmanagedArray ToUnmanagedArray(this Memory source) where T : unmanaged 613 | { 614 | return new UnmanagedArray(source.Span); 615 | } 616 | } 617 | 618 | internal class UnmanagedArrayDebuggerTypeProxy where T : unmanaged 619 | { 620 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 621 | private readonly UnmanagedArray _entity; 622 | 623 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 624 | public T[] Items 625 | { 626 | get 627 | { 628 | var items = new T[_entity.Length]; 629 | _entity.CopyTo(items, 0); 630 | return items; 631 | } 632 | } 633 | 634 | public UnmanagedArrayDebuggerTypeProxy(UnmanagedArray entity) => _entity = entity; 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /src/UnmanagedArray/UnmanagedArray.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;netcoreapp3.1;net48;netstandard2.0;netstandard2.1 5 | 9.0 6 | enable 7 | true 8 | true 9 | ikorin24 10 | ikorin24 11 | An effective tool for unmanaged array. 12 | Copyright © 2021 ikorin24 13 | https://github.com/ikorin24/UnmanagedArray.git 14 | LICENSE 15 | true 16 | git 17 | 2.1.3 18 | UnmanageUtility 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | True 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/UnmanagedArray/UnmanagedList.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 ikorin24 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #nullable enable 27 | 28 | #if NET5_0 || NETCOREAPP3_1 29 | #define FAST_SPAN 30 | #endif 31 | 32 | using System; 33 | using System.Collections; 34 | using System.Collections.Generic; 35 | using System.Diagnostics; 36 | using System.Runtime.InteropServices; 37 | using System.Runtime.CompilerServices; 38 | using System.Buffers; 39 | 40 | namespace UnmanageUtility 41 | { 42 | /// 43 | /// List class which is allocated in unmanaged memory. 44 | /// Only for unmanaged types. (e.g. int, float, recursive-unmanaged struct, and so on.) 45 | /// 46 | /// type of list 47 | [DebuggerTypeProxy(typeof(UnmanagedListDebuggerTypeProxy<>))] 48 | [DebuggerDisplay("UnmanagedList<{typeof(T).Name}>[{_length}]")] 49 | public sealed unsafe class UnmanagedList : IList, IList, IReadOnlyList, IDisposable 50 | where T : unmanaged 51 | { 52 | private const int DefaultCapacity = 4; // DO NOT change into 0 53 | 54 | private RawArray _array; 55 | private int _length; 56 | 57 | /// Get count of elements in the list. 58 | public int Count => _length; 59 | 60 | /// Get or set capacity of current inner array. 61 | public int Capacity 62 | { 63 | get => _array.Length; 64 | set 65 | { 66 | if(value < _length) { 67 | throw new ArgumentOutOfRangeException(nameof(value)); 68 | } 69 | if(value != _array.Length) { 70 | if(value > 0) { 71 | RawArray newArray = default; 72 | try { 73 | newArray = new RawArray(value); 74 | if(_length > 0) { 75 | Buffer.MemoryCopy((void*)_array.Ptr, (void*)newArray.Ptr, newArray.Length * sizeof(T), _length * sizeof(T)); 76 | } 77 | } 78 | catch { 79 | newArray.Dispose(); 80 | throw; 81 | } 82 | _array.Dispose(); 83 | _array = newArray; 84 | } 85 | else { 86 | _array.Dispose(); 87 | _array = default; 88 | } 89 | } 90 | } 91 | } 92 | 93 | /// Get pointer of innner array. (Returns if == 0). 94 | public IntPtr Ptr 95 | { 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | get => (IntPtr)Unsafe.AsPointer(ref GetReference()); 98 | } 99 | 100 | bool ICollection.IsReadOnly => false; 101 | 102 | bool IList.IsFixedSize => false; 103 | 104 | bool IList.IsReadOnly => false; 105 | 106 | bool ICollection.IsSynchronized => false; 107 | 108 | object ICollection.SyncRoot => this; 109 | 110 | #pragma warning disable CS8769 111 | object IList.this[int index] { get => this[index]; set => this[index] = (T)value; } 112 | #pragma warning restore CS8769 113 | 114 | /// Create new instance with default capacity. 115 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 116 | public UnmanagedList() 117 | { 118 | _array = new RawArray(DefaultCapacity); 119 | } 120 | 121 | /// Create new instance with specified capacity. 122 | /// internal capacity of 123 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 124 | public UnmanagedList(int capacity) 125 | { 126 | if(capacity < 0) { ThrowHelper.ArgumentOutOfRange(nameof(capacity)); } 127 | if(capacity != 0) { 128 | _array = new RawArray(capacity); 129 | } 130 | } 131 | 132 | /// Create new instance initialized by specified array. 133 | /// items copied to 134 | public UnmanagedList(T[] items) 135 | { 136 | if(items is null) { ThrowHelper.ArgumentNull(nameof(items)); } 137 | if(items!.Length == 0) { return; } 138 | _array = new RawArray(items!.Length); 139 | items.CopyTo(_array.AsSpan()); 140 | _length = items.Length; 141 | } 142 | 143 | /// Create new instance initialized by specified . 144 | /// items copied to 145 | public UnmanagedList(ReadOnlySpan items) 146 | { 147 | if(items.IsEmpty) { return; } 148 | _array = new RawArray(items.Length); 149 | items.CopyTo(_array.AsSpan()); 150 | _length = items.Length; 151 | } 152 | 153 | /// Create new instance initialized by specified . 154 | /// items copied to 155 | public UnmanagedList(IEnumerable items) : this(capacity: 0) 156 | { 157 | AddRange(items); 158 | } 159 | 160 | /// Finalize instance 161 | ~UnmanagedList() => DisposePrivate(); 162 | 163 | /// Get or set item of type with specified index. 164 | /// index to get or set 165 | /// item of specified index. 166 | public T this[int index] 167 | { 168 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 169 | get => GetReference(index); 170 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 171 | set => GetReference(index) = value; 172 | } 173 | 174 | /// Get reference to head item (Returns ref to null if empty) 175 | /// reference to head item 176 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 177 | public ref T GetReference() 178 | { 179 | return ref Unsafe.AsRef(_length == 0 ? (T*)null : (T*)_array.Ptr); 180 | } 181 | 182 | /// Get reference to item of specified index 183 | /// index to get reference 184 | /// reference to item of specified index 185 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 186 | public ref T GetReference(int index) 187 | { 188 | if((uint)index >= (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(index)); } 189 | return ref _array[index]; 190 | } 191 | 192 | /// Add an item of type 193 | /// an item to add to list 194 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 195 | public void Add(T item) 196 | { 197 | if(_array.Length > _length) { 198 | _array[_length++] = item; 199 | } 200 | else { 201 | // Here is uncommon path. 202 | // Don't inline resizing method, to minimize size of IL in common path. 203 | AddWithResize(item); 204 | } 205 | } 206 | 207 | /// Insert a specified item to specified index 208 | /// index of list to insert item 209 | /// item to insert 210 | public void Insert(int index, T item) 211 | { 212 | // Inserting to end of the list is legal. 213 | if((uint)index > (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(index)); } 214 | if(_array.Length == _length) { 215 | EnsureCapacity(_array.Length + 1); 216 | } 217 | 218 | if(index < _length) { 219 | var moveSpan = _array.AsSpan(index, _length - index); 220 | var dest = _array.AsSpan(index + 1, moveSpan.Length); 221 | moveSpan.CopyTo(dest); 222 | } 223 | _array[index] = item; 224 | _length++; 225 | } 226 | 227 | /// Add items of type 228 | /// items to add to list 229 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 230 | public void AddRange(T[] items) 231 | { 232 | if(items is null) { ThrowHelper.ArgumentNull(nameof(items)); } 233 | AddRange(items.AsSpan()); 234 | } 235 | 236 | /// Add items of type 237 | /// items to add to list 238 | public void AddRange(ReadOnlySpan items) 239 | { 240 | if(items.IsEmpty) { return; } 241 | 242 | // Check whether items are part of self. 243 | if(SpanContainsMemory(_array.AsSpan(), ref MemoryMarshal.GetReference(items))) { 244 | 245 | // Ensure capacity without disposing old array 246 | // because it contains added items. 247 | if(EnsureCapacityWithoutDisposingOld(_length + items.Length, out var old)) { 248 | 249 | // Dispose old array after copying. 250 | using(old) { 251 | items.CopyTo(_array.AsSpan(_length)); 252 | } 253 | } 254 | else { 255 | // In the case that no swapping inner array happend when ensure capacity. 256 | Debug.Assert(old.Length == 0 && old.Ptr == default); 257 | items.CopyTo(_array.AsSpan(_length)); 258 | } 259 | } 260 | else { 261 | EnsureCapacity(_length + items.Length); 262 | items.CopyTo(_array.AsSpan(_length)); 263 | } 264 | _length += items.Length; 265 | } 266 | 267 | /// Add items of type 268 | /// items to add to list 269 | public void AddRange(IEnumerable items) => InsertRange(_length, items); 270 | 271 | /// Increase by the specified count. If there is not enough , it will increase as well. 272 | /// count to increase 273 | /// true to initialize the extended memory, otherwise false 274 | /// of extended memory 275 | public Span Extend(int count, bool zeroFill = true) 276 | { 277 | if(count < 0) { ThrowHelper.ArgumentOutOfRange(nameof(count)); } 278 | if(count == 0) { return Span.Empty; } 279 | 280 | var margin = _array.Length - _length; 281 | if(margin < count) { 282 | Capacity += count - margin; 283 | } 284 | var newSpan = _array.AsSpan(_length, count); 285 | _length += count; 286 | if(zeroFill) { 287 | newSpan.Clear(); 288 | } 289 | return newSpan; 290 | } 291 | 292 | /// Insert items to specified index in the list 293 | /// index to insert 294 | /// items to insert 295 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 296 | public void InsertRange(int index, T[] items) 297 | { 298 | if(items is null) { ThrowHelper.ArgumentNull(nameof(items)); } 299 | InsertRange(index, items.AsSpan()); 300 | } 301 | 302 | /// Insert items to specified index in the list 303 | /// index to insert 304 | /// items to insert 305 | public void InsertRange(int index, ReadOnlySpan items) 306 | { 307 | // Inserting to end of the list is legal. 308 | if((uint)index > (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(index)); } 309 | 310 | if(items.IsEmpty) { return; } 311 | 312 | ref var itemsHead = ref MemoryMarshal.GetReference(items); 313 | // Check whether items are part of self. 314 | if(SpanContainsMemory(_array.AsSpan(), ref itemsHead)) { 315 | 316 | // Ensure capacity without disposing old array 317 | // because it contains added items. 318 | if(EnsureCapacityWithoutCopy(_length + items.Length, out var old)) { 319 | // `items` are in `old`. 320 | using(old) { 321 | var size1 = index * sizeof(T); 322 | var size2 = (_length - index) * sizeof(T); 323 | Buffer.MemoryCopy((void*)old.Ptr, 324 | (void*)_array.Ptr, 325 | size1, 326 | size1); 327 | Buffer.MemoryCopy((T*)old.Ptr + index, 328 | (T*)_array.Ptr + index + items.Length, 329 | size2, 330 | size2); 331 | items.CopyTo(_array.AsSpan(index)); 332 | } 333 | } 334 | else { 335 | Debug.Assert(old.Length == 0 && old.Ptr == default); 336 | // Capacity was not changed, so `items` are in `_array` 337 | 338 | // index (inserting point) 339 | // ------------------------------↓------------------------------------------------- 340 | // | _array[0] | _array[1] | ... | _array[index] | ... | _array[_array.Length - 1] | 341 | // | firstHalf | secondHalf | 342 | // ------------------------------+-------------------------------------------------- 343 | // | 344 | // [case 1] | 345 | // | <-- items --> | | 346 | // [case 2] | 347 | // | <---- items ----> | 348 | // [case 3] | 349 | // | <---- items ----> | 350 | 351 | var firstHalf = _array.AsSpan(0, index); 352 | var secondHalf = _array.AsSpan(index, _length - index); 353 | ref var itemsTail = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetReference(items), (IntPtr)((items.Length - 1) * sizeof(T))); 354 | 355 | var isItemsHeadInFirst = SpanContainsMemory(firstHalf, ref itemsHead); 356 | var isItemsTailInFirst = SpanContainsMemory(firstHalf, ref itemsTail); 357 | if(isItemsHeadInFirst && isItemsTailInFirst) { 358 | // case 1 359 | secondHalf.CopyTo(_array.AsSpan(index + items.Length)); 360 | items.CopyTo(_array.AsSpan(index, items.Length)); 361 | } 362 | else if(!isItemsHeadInFirst && !isItemsTailInFirst) { 363 | // case 3 364 | var offsetFromInsertion = Unsafe.ByteOffset(ref MemoryMarshal.GetReference(secondHalf), ref itemsHead); 365 | var secondHalfAfterMove = _array.AsSpan(index + items.Length, secondHalf.Length); 366 | secondHalf.CopyTo(secondHalfAfterMove); 367 | ref var itemsHeadAfterMove = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetReference(secondHalfAfterMove), offsetFromInsertion); 368 | 369 | // `items` are on unmanaged memory in this path, 370 | // so it is no problem to create Span from void* pointer. 371 | // (in the runtime of slow span like net48 or netcoreapp2.0) 372 | var itemsAfterMove = new Span(Unsafe.AsPointer(ref itemsHeadAfterMove), items.Length); 373 | itemsAfterMove.CopyTo(_array.AsSpan(index, items.Length)); 374 | } 375 | else { 376 | // case 2 377 | Debug.Assert(isItemsHeadInFirst != isItemsTailInFirst); 378 | 379 | var itemsByteLenInFirst = Unsafe.ByteOffset(ref itemsHead, ref MemoryMarshal.GetReference(secondHalf)); 380 | var secondHalfAfterMove = _array.AsSpan(index + items.Length, secondHalf.Length); 381 | secondHalf.CopyTo(secondHalfAfterMove); 382 | var itemsPartInFirst = MemoryMarshal.Cast(items) 383 | .Slice(0, itemsByteLenInFirst.ToInt32()); 384 | var insertionSpan = MemoryMarshal.Cast(_array.AsSpan(index, items.Length)); 385 | itemsPartInFirst.CopyTo(insertionSpan); 386 | var itemsPartInSecondAfterMove = MemoryMarshal.Cast(secondHalfAfterMove) 387 | .Slice(0, items.Length * sizeof(T) - itemsByteLenInFirst.ToInt32()); 388 | itemsPartInSecondAfterMove.CopyTo(insertionSpan.Slice(itemsByteLenInFirst.ToInt32())); 389 | } 390 | } 391 | } 392 | else { 393 | EnsureCapacity(_length + items.Length); 394 | if(index < _length) { 395 | var moveSpan = _array.AsSpan(index, _length - index); 396 | var dest = _array.AsSpan(index + items.Length, moveSpan.Length); 397 | moveSpan.CopyTo(dest); 398 | } 399 | items.CopyTo(_array.AsSpan(index)); 400 | } 401 | _length += items.Length; 402 | } 403 | 404 | /// Insert items to specified index in the list 405 | /// index to insert 406 | /// items to insert 407 | public void InsertRange(int index, IEnumerable items) 408 | { 409 | // Inserting to end of the list is legal. 410 | if((uint)index > (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(index)); } 411 | if(items is null) { ThrowHelper.ArgumentNull(nameof(items)); } 412 | 413 | if(items is UnmanagedList ul) { 414 | InsertRange(index, ul.AsSpan()); 415 | } 416 | else if(items is T[] a) { 417 | InsertRange(index, a.AsSpan()); 418 | } 419 | else if(items is UnmanagedArray ua) { 420 | InsertRange(index, ua.AsSpan()); 421 | } 422 | // For future version in .NET 5 423 | //else if(items is List l) { 424 | // InsertRange(index, CollectionsMarshal.AsSpan(l)); 425 | //} 426 | else if(items is ICollection c) { 427 | // ICollection.CopyTo needs T[] argument. 428 | // Copy to a buffer array from ArrayPool.Shared and re-copy to this._array. 429 | // This is faster than each-item-iterating insertion. 430 | 431 | // items must not be `this` because EnsureCapacity may free memory of items. 432 | Debug.Assert(items != this); 433 | var count = c.Count; 434 | if(count > 0) { 435 | EnsureCapacity(_length + count); 436 | if(index < _length) { 437 | var moveSpan = _array.AsSpan(index, _length - index); 438 | var dest = _array.AsSpan(index + count, moveSpan.Length); 439 | moveSpan.CopyTo(dest); 440 | } 441 | var buf = ArrayPool.Shared.Rent(count); 442 | try { 443 | c.CopyTo(buf, 0); 444 | buf.AsSpan(0, count).CopyTo(_array.AsSpan(index)); 445 | } 446 | finally { 447 | ArrayPool.Shared.Return(buf); 448 | } 449 | _length += count; 450 | } 451 | } 452 | else { 453 | foreach(var item in items!) { 454 | Insert(index++, item); 455 | } 456 | } 457 | } 458 | 459 | // No inlining because this is uncommon path. 460 | [MethodImpl(MethodImplOptions.NoInlining)] 461 | private void AddWithResize(T item) 462 | { 463 | int length = _length; 464 | EnsureCapacity(length + 1); 465 | _length = length + 1; 466 | _array[length] = item; 467 | } 468 | 469 | private void EnsureCapacity(int min) 470 | { 471 | if(EnsureCapacityWithoutDisposingOld(min, out var old)) { 472 | old.Dispose(); 473 | } 474 | } 475 | 476 | private bool EnsureCapacityWithoutDisposingOld(int min, out RawArray old) 477 | { 478 | if(EnsureCapacityWithoutCopy(min, out old)) { 479 | if(_length > 0) { 480 | Buffer.MemoryCopy((void*)old.Ptr, (void*)_array.Ptr, _array.GetSizeInBytes(), _length * sizeof(T)); 481 | } 482 | return true; 483 | } 484 | else { 485 | return false; 486 | } 487 | } 488 | 489 | private bool EnsureCapacityWithoutCopy(int min, out RawArray old) 490 | { 491 | if(_array.Length < min) { 492 | int newCapacity = _array.Length; 493 | do { 494 | // throw exception if newCapacity is overflow 495 | checked { 496 | newCapacity = newCapacity == 0 ? DefaultCapacity : newCapacity * 2; 497 | } 498 | } while(newCapacity < min); 499 | 500 | var newArray = new RawArray(newCapacity); 501 | old = _array; 502 | _array = newArray; 503 | return true; 504 | } 505 | else { 506 | old = default; 507 | return false; 508 | } 509 | } 510 | 511 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 512 | private bool SpanContainsMemory(Span span, ref T target) 513 | { 514 | // [memory space] 515 | // ← small address big address → 516 | // ... | head | ... | tail | ... 517 | // 518 | // ... | ////////////////////// | ... 519 | // ... | memory range | ... 520 | // ... | ////////////////////// | ... 521 | // 522 | // ... | !LessThan(ref target, ref head) 523 | // !GreaterThan(ref target, ref tail) | ... 524 | 525 | return span.Length > 0 && 526 | !Unsafe.IsAddressLessThan(ref target, ref MemoryMarshal.GetReference(span)) && 527 | !Unsafe.IsAddressGreaterThan(ref target, ref Unsafe.AddByteOffset(ref MemoryMarshal.GetReference(span), (IntPtr)((span.Length - 1) * sizeof(T)))); 528 | } 529 | 530 | /// 531 | /// Dispose this instance and release unmanaged memory. 532 | /// If already disposed, do nothing. 533 | /// 534 | public void Dispose() 535 | { 536 | DisposePrivate(); 537 | GC.SuppressFinalize(this); 538 | } 539 | 540 | /// Get inner memory as . 541 | /// inner memory as 542 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 543 | public Span AsSpan() 544 | { 545 | return new Span((T*)_array.Ptr, _length); 546 | } 547 | 548 | /// Get inner memory as with specified start index. 549 | /// start index of inner memory 550 | /// inner memory as with specified start index 551 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 552 | public Span AsSpan(int start) 553 | { 554 | if((uint)start > (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(start)); } 555 | return _array.AsSpan(start, _length - start); 556 | } 557 | 558 | /// Get inner memory as with specified start index and specified length. 559 | /// start index of inner memory 560 | /// length of inner memory 561 | /// inner memory as with specified start index and specified length. 562 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 563 | public Span AsSpan(int start, int length) 564 | { 565 | if((uint)start > (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(start)); } 566 | if((uint)length > (uint)(_length - start)) { ThrowHelper.ArgumentOutOfRange(nameof(length)); } 567 | 568 | return _array.AsSpan(start, length); 569 | } 570 | 571 | private void DisposePrivate() 572 | { 573 | if(_array.Ptr != IntPtr.Zero) { 574 | _array.Dispose(); 575 | _array = default; 576 | _length = 0; 577 | } 578 | } 579 | 580 | 581 | /// Get index of specified item. Returns -1 if not contains . 582 | /// item to get index 583 | /// index in the list, or -1 if not contains 584 | public int IndexOf(T item) 585 | { 586 | for(int i = 0; i < _length; i++) { 587 | if(EqualityComparer.Default.Equals(_array[i], item)) { 588 | return i; 589 | } 590 | } 591 | return -1; 592 | } 593 | 594 | /// Remove item of specified index 595 | /// index to remove from list 596 | public void RemoveAt(int index) 597 | { 598 | if((uint)index >= (uint)_length) { ThrowHelper.ArgumentOutOfRange(nameof(index)); } 599 | _length--; 600 | if(index < _length) { 601 | _array.AsSpan(index + 1).CopyTo(_array.AsSpan(index)); 602 | } 603 | } 604 | 605 | /// Clear items in the list. 606 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 607 | public void Clear() 608 | { 609 | _length = 0; 610 | } 611 | 612 | internal void TransferInnerMemoryOwnership(out IntPtr ptr, out int capacity, out int length) 613 | { 614 | ptr = _array.Ptr; 615 | capacity = _array.Length; 616 | length = _length; 617 | _array = default; 618 | _length = 0; 619 | } 620 | 621 | /// Get whether specified item is in the list. 622 | /// target item to check 623 | /// true if contains, false if not contain 624 | public bool Contains(T item) 625 | { 626 | for(int i = 0; i < _length; i++) { 627 | if(EqualityComparer.Default.Equals(_array[i], item)) { 628 | return true; 629 | } 630 | } 631 | return false; 632 | } 633 | 634 | /// Copy items to specified array of specified start index. 635 | /// 636 | /// 637 | public void CopyTo(T[] array, int arrayIndex) 638 | { 639 | if(_array.Length == 0) { return; } 640 | AsSpan().CopyTo(array.AsSpan(arrayIndex)); 641 | } 642 | 643 | /// Remove specified item from list. Returns true if item is removed, false if not contains item in the list. 644 | /// an item to remove from list 645 | /// true if item is removed, false if not contains item in the list 646 | public bool Remove(T item) 647 | { 648 | var index = IndexOf(item); 649 | if(index >= 0) { 650 | RemoveAt(index); 651 | return true; 652 | } 653 | else { 654 | return false; 655 | } 656 | } 657 | 658 | /// Get enumerator of the list 659 | /// enumerator of the list 660 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 661 | public Enumerator GetEnumerator() => new Enumerator(this); 662 | 663 | IEnumerator IEnumerable.GetEnumerator() => new EnumeratorClass(this); 664 | 665 | IEnumerator IEnumerable.GetEnumerator() => new EnumeratorClass(this); 666 | 667 | #pragma warning disable CS8769 668 | int IList.Add(object value) 669 | { 670 | Add((T)value); 671 | return _length; 672 | } 673 | 674 | bool IList.Contains(object value) => Contains((T)value); 675 | 676 | int IList.IndexOf(object value) => IndexOf((T)value); 677 | 678 | void IList.Insert(int index, object value) => Insert(index, (T)value); 679 | 680 | void IList.Remove(object value) => Remove((T)value); 681 | 682 | void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index); 683 | #pragma warning restore CS8769 684 | 685 | 686 | /// Enumerator struct of 687 | public struct Enumerator : IEnumerator 688 | { 689 | private readonly UnmanagedList _list; 690 | private T _current; 691 | private int _index; 692 | 693 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 694 | internal Enumerator(UnmanagedList list) 695 | { 696 | _list = list; 697 | _index = 0; 698 | _current = default; 699 | } 700 | 701 | /// Get current item 702 | public T Current => _current; 703 | 704 | object IEnumerator.Current => _current; 705 | 706 | /// Dispose this enumerator (but do nothing) 707 | public void Dispose() { } // nop 708 | 709 | /// Move to next item 710 | /// true if continue, false to end 711 | public bool MoveNext() 712 | { 713 | if(_index < _list._length) { 714 | _current = _list[_index]; 715 | _index++; 716 | return true; 717 | } 718 | else { 719 | return false; 720 | } 721 | } 722 | 723 | /// Reset enumerator 724 | public void Reset() 725 | { 726 | _index = 0; 727 | _current = default; 728 | } 729 | } 730 | 731 | /// Enumerator class of 732 | public class EnumeratorClass : IEnumerator 733 | { 734 | private readonly UnmanagedList _list; 735 | private T _current; 736 | private int _index; 737 | 738 | internal EnumeratorClass(UnmanagedList list) 739 | { 740 | _list = list; 741 | _index = 0; 742 | _current = default; 743 | } 744 | 745 | /// Get current item 746 | public T Current => _current; 747 | 748 | object IEnumerator.Current => _current; 749 | 750 | /// Dispose this enumerator (but do nothing) 751 | public void Dispose() { } // nop 752 | 753 | /// Move to next item 754 | /// true if continue, false to end 755 | public bool MoveNext() 756 | { 757 | if(_index < _list._length) { 758 | _current = _list[_index]; 759 | _index++; 760 | return true; 761 | } 762 | else { 763 | return false; 764 | } 765 | } 766 | 767 | /// Reset enumerator 768 | public void Reset() 769 | { 770 | _index = 0; 771 | _current = default; 772 | } 773 | } 774 | 775 | 776 | /// 777 | /// Raw array struct. This struct checks no index boundary and any other safety. 778 | /// 779 | private readonly struct RawArray : IDisposable 780 | { 781 | /// Raw array pointer 782 | public readonly IntPtr Ptr; 783 | /// Raw array length (this is NOT size in bytes) 784 | public readonly int Length; 785 | 786 | public ref T this[int index] 787 | { 788 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 789 | get => ref ((T*)Ptr)[index]; 790 | } 791 | 792 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 793 | public RawArray(int length) 794 | { 795 | // Not clear by zero for performance. 796 | // This is no problem because T is unmanaged type. 797 | 798 | Ptr = Marshal.AllocHGlobal(length * sizeof(T)); 799 | Length = length; 800 | } 801 | 802 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 803 | public int GetSizeInBytes() => Length * sizeof(T); 804 | 805 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 806 | public Span AsSpan() 807 | { 808 | #if FAST_SPAN 809 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef((T*)Ptr), Length); 810 | #else 811 | return new Span((T*)Ptr, Length); 812 | #endif 813 | } 814 | 815 | // No boundary checking. Be careful !! 816 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 817 | public Span AsSpan(int start) 818 | { 819 | #if FAST_SPAN 820 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef(((T*)Ptr) + start), Length - start); 821 | #else 822 | return new Span(((T*)Ptr) + start, Length - start); 823 | #endif 824 | } 825 | 826 | // No boundary checking. Be careful !! 827 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 828 | public Span AsSpan(int start, int length) 829 | { 830 | #if FAST_SPAN 831 | return MemoryMarshal.CreateSpan(ref Unsafe.AsRef(((T*)Ptr) + start), length); 832 | #else 833 | return new Span(((T*)Ptr) + start, length); 834 | #endif 835 | } 836 | 837 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 838 | public void Dispose() 839 | { 840 | // Clear memory if debug for easy detecting invalid memory access. 841 | // (e.g. Accessing to memory after free) 842 | #if DEBUG 843 | AsSpan().Clear(); 844 | #endif 845 | Marshal.FreeHGlobal(Ptr); 846 | } 847 | } 848 | } 849 | 850 | /// Define extension methods of 851 | public static class UnmanagedListExtension 852 | { 853 | /// Create new from array of type . 854 | /// type of elements 855 | /// source array to initialize 856 | /// new initialized by 857 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 858 | public static UnmanagedList ToUnmanagedList(this T[] source) where T : unmanaged 859 | { 860 | return new UnmanagedList(source); 861 | } 862 | 863 | /// Create new from . 864 | /// type of elements 865 | /// source to initialize 866 | /// new initialized by 867 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 868 | public static UnmanagedList ToUnmanagedList(this Span source) where T : unmanaged 869 | { 870 | return new UnmanagedList(source); 871 | } 872 | 873 | /// Create new from . 874 | /// type of elements 875 | /// source to initialize 876 | /// new initialized by 877 | public static UnmanagedList ToUnmanagedList(this IEnumerable source) where T : unmanaged 878 | { 879 | return new UnmanagedList(source); 880 | } 881 | 882 | /// Create new from . 883 | /// type of elements 884 | /// source to initialize 885 | /// new initialized by 886 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 887 | public static UnmanagedList ToUnmanagedList(this ReadOnlySpan source) where T : unmanaged 888 | { 889 | return new UnmanagedList(source); 890 | } 891 | } 892 | 893 | internal class UnmanagedListDebuggerTypeProxy where T : unmanaged 894 | { 895 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 896 | private readonly UnmanagedList _entity; 897 | 898 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 899 | public T[] Items 900 | { 901 | get 902 | { 903 | var items = new T[_entity.Count]; 904 | _entity.CopyTo(items, 0); 905 | return items; 906 | } 907 | } 908 | 909 | public UnmanagedListDebuggerTypeProxy(UnmanagedList entity) => _entity = entity; 910 | } 911 | } 912 | --------------------------------------------------------------------------------